#include "mslocusr.h"
#include "msluglob.h"
#include "profiles.h"

#include <regentry.h>

#include <ole2.h>

CLUDatabase::CLUDatabase(void)
	: m_cRef(0),
	  m_CurrentUser(NULL)
{
    RefThisDLL(TRUE);
}


CLUDatabase::~CLUDatabase(void)
{
	if (m_CurrentUser != NULL) {
		m_CurrentUser->Release();
		m_CurrentUser = NULL;
	}
    RefThisDLL(FALSE);
}


STDMETHODIMP CLUDatabase::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
	if (!IsEqualIID(riid, IID_IUnknown) &&
		!IsEqualIID(riid, IID_IUserDatabase)) {
        *ppvObj = NULL;
		return ResultFromScode(E_NOINTERFACE);
	}

	*ppvObj = this;
	AddRef();
	return NOERROR;
}


STDMETHODIMP_(ULONG) CLUDatabase::AddRef(void)
{
	return ++m_cRef;
}


STDMETHODIMP_(ULONG) CLUDatabase::Release(void)
{
	ULONG cRef;

	cRef = --m_cRef;

	if (0L == m_cRef) {
		delete this;
	}
	/* Handle circular refcount because of cached current user object. */
	else if (1L == m_cRef && m_CurrentUser != NULL) {
		IUser *pCurrentUser = m_CurrentUser;
		m_CurrentUser = NULL;
		pCurrentUser->Release();
	}

	return cRef;
}


STDMETHODIMP CLUDatabase::Install(LPCSTR pszSupervisorName,
								  LPCSTR pszSupervisorPassword,
								  LPCSTR pszRatingsPassword,
								  IUserProfileInit *pInit)
{
	/* If the system already has a supervisor password, make sure the caller's
	 * password matches.  If there isn't already a password, the caller's
	 * (account) password is it.  We use the account password because the
	 * caller (the setup program) probably didn't pass us a ratings password
	 * in that case -- he also checks to see if there's an old ratings
	 * password and knows to prompt for one only if it's already there.
	 */
	HRESULT hres = ::VerifySupervisorPassword(pszRatingsPassword);
	
	if (FAILED(hres)) {
        if (pszRatingsPassword == NULL)
    		pszRatingsPassword = pszSupervisorPassword;
		::ChangeSupervisorPassword(::szNULL, pszRatingsPassword);
	}
	else if (hres == S_FALSE)
		return E_ACCESSDENIED;


	/* User profiles and password caching have to be enabled for us to work.
	 * We also have to be able to open or create the supervisor's PWL using
	 * the given password.  Thus we validate the password at the same time.
	 */

	{
		RegEntry re(::szLogonKey, HKEY_LOCAL_MACHINE);
		if (re.GetError() != ERROR_SUCCESS)
			return HRESULT_FROM_WIN32(re.GetError());
		if (!re.GetNumber(::szUserProfiles))
			re.SetValue(::szUserProfiles, 1);
		if (re.GetError() != ERROR_SUCCESS)
			return HRESULT_FROM_WIN32(re.GetError());
	}

    /* Make copies of the username and password for passing to the PWL APIs.
     * They need to be in OEM (PWL is accessible from DOS), and must be upper
     * case since the Windows logon dialog uppercases all PWL passwords.
     */
    NLS_STR nlsPWLName(pszSupervisorName);
    NLS_STR nlsPWLPassword(pszSupervisorPassword);
    if (nlsPWLName.QueryError() != ERROR_SUCCESS)
        return HRESULT_FROM_WIN32(nlsPWLName.QueryError());
    if (nlsPWLPassword.QueryError() != ERROR_SUCCESS)
        return HRESULT_FROM_WIN32(nlsPWLPassword.QueryError());
    nlsPWLName.strupr();
    nlsPWLName.ToOEM();
    nlsPWLPassword.strupr();
    nlsPWLPassword.ToOEM();

	HPWL hPWL = NULL;
	APIERR err = ::OpenPasswordCache(&hPWL, nlsPWLName.QueryPch(),
									 nlsPWLPassword.QueryPch(), TRUE);

	if (err != ERROR_SUCCESS) {
        if (err != IERR_IncorrectUsername)
    		err = ::CreatePasswordCache(&hPWL, nlsPWLName.QueryPch(), nlsPWLPassword.QueryPch());
		if (err != ERROR_SUCCESS)	
			return HRESULT_FROM_WIN32(err);
	}


	/* Now that the system has a supervisor password, call a worker function
	 * to clone the supervisor account from the default profile.  The worker
	 * function assumes that the caller has validated that the current user is
	 * a supervisor.
	 */

	err = ::MakeSupervisor(hPWL, pszRatingsPassword);
	::ClosePasswordCache(hPWL, TRUE);
	if (err != ERROR_SUCCESS)
		return HRESULT_FROM_WIN32(err);

	IUser *pSupervisor = NULL;
    hres = GetUser(pszSupervisorName, &pSupervisor);
	if (FAILED(hres)) {
		hres = CreateUser(pszSupervisorName, NULL, TRUE, pInit);
		if (pSupervisor != NULL) {
			pSupervisor->Release();
			pSupervisor = NULL;
		}
		if (SUCCEEDED(hres))
			hres = GetUser(pszSupervisorName, &pSupervisor);	/* reinitialize with created profile */
	}

	if (pSupervisor != NULL) {
		if (SUCCEEDED(hres))
			hres = pSupervisor->Authenticate(pszSupervisorPassword);
		if (SUCCEEDED(hres))
			hres = SetCurrentUser(pSupervisor);
        if (SUCCEEDED(hres))
            pSupervisor->SetSupervisorPrivilege(TRUE, pszRatingsPassword);  /* set appears-supervisor flag */

		pSupervisor->Release();
		pSupervisor = NULL;
	}

	return hres;
}


/* Some install stubs are "clone-user" install stubs, that get re-run if a
 * profile is cloned to become a new user's profile.  For example, if you
 * clone Fred to make Barney, Outlook Express doesn't want Barney to inherit
 * Fred's mailbox.
 *
 * When you run the go-multiuser wizard, we assume that the first user being
 * created is the one who's been using the machine all along, so that one
 * copy should be exempt from this.  So we go through all the install stub
 * keys for the newly created profile and, for any that are marked with a
 * username (even a blank one indicates that it's a clone-user install stub),
 * we mark it with the new username so it won't get re-run.
 */
void FixInstallStubs(LPCSTR pszName, HKEY hkeyProfile)
{
    HKEY hkeyList;
    LONG err = RegOpenKeyEx(hkeyProfile, "Software\\Microsoft\\Active Setup\\Installed Components", 0,
                            KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &hkeyList);

    if (err == ERROR_SUCCESS) {
        DWORD cbKeyName, iKey;
        TCHAR szKeyName[80];

        /* Enumerate components that are installed for the profile. */
        for (iKey = 0; ; iKey++)
        {
            LONG lEnum;

            cbKeyName = ARRAYSIZE(szKeyName);

            if ((lEnum = RegEnumKey(hkeyList, iKey, szKeyName, cbKeyName)) == ERROR_MORE_DATA)
            {
                // ERROR_MORE_DATA means the value name or data was too large
                // skip to the next item
                continue;
            }
            else if( lEnum != ERROR_SUCCESS )
            {
                // could be ERROR_NO_MORE_ENTRIES, or some kind of failure
                // we can't recover from any other registry problem, anyway
                break;
            }

            HKEY hkeyComponent;
            if (RegOpenKeyEx(hkeyList, szKeyName, 0,
                             KEY_QUERY_VALUE | KEY_SET_VALUE, &hkeyComponent) == ERROR_SUCCESS) {
                cbKeyName = sizeof(szKeyName);
                err = RegQueryValueEx(hkeyComponent, "Username", NULL, NULL,
                                      (LPBYTE)szKeyName, &cbKeyName);
                if (err == ERROR_SUCCESS || err == ERROR_MORE_DATA) {
                    RegSetValueEx(hkeyComponent, "Username",
                                  0, REG_SZ,
                                  (LPBYTE)pszName,
                                  lstrlen(pszName)+1);
                }
                RegCloseKey(hkeyComponent);
            }
        }
        RegCloseKey(hkeyList);
    }
}


STDMETHODIMP CLUDatabase::CreateUser(LPCSTR pszName, IUser *pCloneFrom,
                                     BOOL fFixInstallStubs, IUserProfileInit *pInit)
{
    if (::strlenf(pszName) > cchMaxUsername)
        return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);

	RegEntry reRoot(::szProfileList, HKEY_LOCAL_MACHINE);
	if (reRoot.GetError() != ERROR_SUCCESS)
		return HRESULT_FROM_WIN32(reRoot.GetError());

	/* See if the user's subkey exists.  If it doesn't, create it. */
	reRoot.MoveToSubKey(pszName);
	if (reRoot.GetError() != ERROR_SUCCESS) {
		RegEntry reUser(pszName, reRoot.GetKey());
		if (reUser.GetError() != ERROR_SUCCESS)
			return HRESULT_FROM_WIN32(reUser.GetError());

		reRoot.MoveToSubKey(pszName);
		if (reRoot.GetError() != ERROR_SUCCESS)
			return HRESULT_FROM_WIN32(reRoot.GetError());
	}

	NLS_STR nlsProfilePath(MAX_PATH);
	if (nlsProfilePath.QueryError() != ERROR_SUCCESS)
		return E_OUTOFMEMORY;

	reRoot.GetValue(::szProfileImagePath, &nlsProfilePath);

	/* If the profile path is already recorded for the user, see if the
	 * profile itself exists.  If it does, then CreateUser is an error.
	 */
	BOOL fComputePath = FALSE;

	if (reRoot.GetError() == ERROR_SUCCESS) {
		if (!DirExists(nlsProfilePath.QueryPch())) {
			if (!::CreateDirectory(nlsProfilePath.QueryPch(), NULL)) {
				fComputePath = TRUE;
			}
		}
	}
	else {
		fComputePath = TRUE;
	}

	if (fComputePath) {
		ComputeLocalProfileName(pszName, &nlsProfilePath);
		reRoot.SetValue(::szProfileImagePath, nlsProfilePath.QueryPch());
	}

	AddBackslash(nlsProfilePath);
	nlsProfilePath.strcat(::szStdNormalProfile);
	if (FileExists(nlsProfilePath.QueryPch()))
		return HRESULT_FROM_WIN32(ERROR_USER_EXISTS);

	/* The user's profile directory now exists, and its path is recorded
	 * in the registry.  nlsProfilePath is now the full pathname for the
	 * user's profile file, which does not exist yet.
	 */

	NLS_STR nlsOtherProfilePath(MAX_PATH);
	if (nlsOtherProfilePath.QueryError() != ERROR_SUCCESS)
		return HRESULT_FROM_WIN32(nlsOtherProfilePath.QueryError());

	HRESULT hres;
	DWORD cbPath = nlsOtherProfilePath.QueryAllocSize();
	if (pCloneFrom == NULL ||
		FAILED(pCloneFrom->GetProfileDirectory(nlsOtherProfilePath.Party(), &cbPath)))
	{
		/* Cloning default profile. */

		hres = GiveUserDefaultProfile(nlsProfilePath.QueryPch());
        nlsOtherProfilePath.DonePartying();
        nlsOtherProfilePath = "";
	}
	else {
		/* Cloning other user's profile. */
        nlsOtherProfilePath.DonePartying();
		AddBackslash(nlsOtherProfilePath);
		nlsOtherProfilePath.strcat(::szStdNormalProfile);
		hres = CopyProfile(nlsOtherProfilePath.QueryPch(), nlsProfilePath.QueryPch());
	}

	if (FAILED(hres))
		return hres;

	/* Now the user has a profile.  Load it and perform directory
	 * reconciliation.
	 */

	LONG err = ::MyRegLoadKey(HKEY_USERS, pszName, nlsProfilePath.QueryPch());
	if (err == ERROR_SUCCESS) {
		HKEY hkeyNewProfile;
		err = ::RegOpenKey(HKEY_USERS, pszName, &hkeyNewProfile);
		if (err == ERROR_SUCCESS) {

            /* Build just the profile directory, no "user.dat" on the end. */
        	ISTR istrBackslash(nlsProfilePath);
	        if (nlsProfilePath.strrchr(&istrBackslash, '\\')) {
                ++istrBackslash;
		        nlsProfilePath.DelSubStr(istrBackslash);
            }

            if (pInit != NULL) {
                hres = pInit->PreInitProfile(hkeyNewProfile, nlsProfilePath.QueryPch());
                if (hres == E_NOTIMPL)
                    hres = S_OK;
            }
            else
                hres = S_OK;

            if (SUCCEEDED(hres)) {
    			err = ReconcileFiles(hkeyNewProfile, nlsProfilePath, nlsOtherProfilePath);	/* modifies nlsProfilePath */
                hres = HRESULT_FROM_WIN32(err);

                if (fFixInstallStubs) {
                    ::FixInstallStubs(pszName, hkeyNewProfile);
                }

                if (pInit != NULL) {
                    hres = pInit->PostInitProfile(hkeyNewProfile, nlsProfilePath.QueryPch());
                    if (hres == E_NOTIMPL)
                        hres = S_OK;
                }
            }
			::RegFlushKey(hkeyNewProfile);
			::RegCloseKey(hkeyNewProfile);
		}
		::RegUnLoadKey(HKEY_USERS, pszName);
	}

	return hres;
}


STDMETHODIMP CLUDatabase::AddUser(LPCSTR pszName, IUser *pSourceUser,
                                  IUserProfileInit *pInit, IUser **ppOut)
{
	if (ppOut != NULL)
		*ppOut = NULL;

    if (IsCurrentUserSupervisor(this) != S_OK)
		return E_ACCESSDENIED;

	HRESULT hres = CreateUser(pszName, pSourceUser, FALSE, pInit);
	if (FAILED(hres))
		return hres;

	if (ppOut != NULL)
		hres = GetUser(pszName, ppOut);

	return hres;
}


STDMETHODIMP CLUDatabase::GetUser(LPCSTR pszName, IUser **ppOut)
{
	*ppOut = NULL;

	CLUUser *pUser = new CLUUser(this);

	if (pUser == NULL) {
		return ResultFromScode(E_OUTOFMEMORY);
	}

	HRESULT err = pUser->Init(pszName);
	if (SUCCEEDED(err) && !pUser->Exists()) {
		err = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
	}

	if (FAILED(err) || !pUser->Exists()) {
		pUser->Release();
		return err;
	}

	*ppOut = pUser;

	return NOERROR;
}


STDMETHODIMP CLUDatabase::GetSpecialUser(DWORD nSpecialUserCode, IUser **ppOut)
{
	switch (nSpecialUserCode) {
	case GSU_CURRENT:
		return GetCurrentUser(ppOut);
		break;

	case GSU_DEFAULT:
		return GetUser(szDefaultUserName, ppOut);
		break;

	default:
		return ResultFromScode(E_INVALIDARG);
	};

	return NOERROR;
}


HRESULT GetSystemCurrentUser(NLS_STR *pnlsCurrentUser)
{
	DWORD cbBuffer = pnlsCurrentUser->QueryAllocSize();
	UINT err;
	if (!::GetUserName(pnlsCurrentUser->Party(), &cbBuffer))
		err = ::GetLastError();
	else
		err = NOERROR;
	pnlsCurrentUser->DonePartying();

	return HRESULT_FROM_WIN32(err);
}


STDMETHODIMP CLUDatabase::GetCurrentUser(IUser **ppOut)
{
	if (m_CurrentUser == NULL) {
		NLS_STR nlsCurrentUser(cchMaxUsername+1);
		UINT err = nlsCurrentUser.QueryError();
		if (err)
			return HRESULT_FROM_WIN32(err);

		HRESULT hres = GetSystemCurrentUser(&nlsCurrentUser);
		if (FAILED(hres))
			return hres;

		hres = GetUser(nlsCurrentUser.QueryPch(), (IUser **)&m_CurrentUser);
		if (FAILED(hres))
			return hres;
	}

	*ppOut = m_CurrentUser;
	m_CurrentUser->AddRef();

	return NOERROR;
}


STDMETHODIMP CLUDatabase::SetCurrentUser(IUser *pUser)
{
	CLUUser *pCLUUser = (CLUUser *)pUser;
	HPWL hpwlUser;
	if (!pCLUUser->m_fAuthenticated ||
		FAILED(pCLUUser->GetPasswordCache(pCLUUser->m_nlsPassword.QueryPch(), &hpwlUser)))
	{
		return HRESULT_FROM_WIN32(ERROR_NOT_AUTHENTICATED);
	}
	::ClosePasswordCache(hpwlUser, TRUE);

	CLUUser *pClone;

	HRESULT hres = GetUser(pCLUUser->m_nlsUsername.QueryPch(), (IUser **)&pClone);
	if (FAILED(hres))
		return hres;

	/* Make sure the clone object is authenticated properly. */
	hres = pClone->Authenticate(pCLUUser->m_nlsPassword.QueryPch());
	if (FAILED(hres)) {
		return HRESULT_FROM_WIN32(ERROR_NOT_AUTHENTICATED);
	}

	if (m_CurrentUser != NULL) {
		m_CurrentUser->Release();
	}

	m_CurrentUser = pClone;
	return NOERROR;
}


STDMETHODIMP CLUDatabase::DeleteUser(LPCSTR pszName)
{
	NLS_STR nlsName(MAX_PATH);
	if (nlsName.QueryError() != ERROR_SUCCESS)
		return HRESULT_FROM_WIN32(nlsName.QueryError());

    /* Check supervisor privilege up front, this'll handle the not-logged-on
     * case later if we re-enable supervisor stuff.
     */
    if (IsCurrentUserSupervisor(this) != S_OK)
        return E_ACCESSDENIED;

	IUser *pCurrentUser;

    HRESULT hres = GetCurrentUser(&pCurrentUser);
    if (SUCCEEDED(hres)) {

    	/* Check current user's name and make sure we're not deleting him.
    	 * Note that because the current user must be an authenticated supervisor,
    	 * and you can't delete the current user, you can never delete the last
    	 * supervisor using this function.
    	 */
    	DWORD cb = nlsName.QueryAllocSize();
    	hres = pCurrentUser->GetName(nlsName.Party(), &cb);
    	nlsName.DonePartying();
    	if (SUCCEEDED(hres) && !::stricmpf(pszName, nlsName.QueryPch()))
            hres = HRESULT_FROM_WIN32(ERROR_BUSY);

    	if (FAILED(hres))
            return hres;
    }

    /* Check system's idea of current user as well. */

    hres = GetSystemCurrentUser(&nlsName);
    if (SUCCEEDED(hres)) {
        if (!::stricmpf(pszName, nlsName.QueryPch()))
            return HRESULT_FROM_WIN32(ERROR_BUSY);
    }

    return DeleteProfile(pszName);
}


STDMETHODIMP CLUDatabase::RenameUser(LPCSTR pszOldName, LPCSTR pszNewName)
{
	return ResultFromScode(E_NOTIMPL);
}


STDMETHODIMP CLUDatabase::EnumUsers(IEnumUnknown **ppOut)
{
	*ppOut = NULL;

	CLUEnum *pEnum = new CLUEnum(this);

	if (pEnum == NULL) {
		return ResultFromScode(E_OUTOFMEMORY);
	}

	HRESULT err = pEnum->Init();
	if (FAILED(err)) {
		pEnum->Release();
		return err;
	}

	*ppOut = pEnum;

	return NOERROR;
}



STDMETHODIMP CLUDatabase::Authenticate(HWND hwndOwner, DWORD dwFlags,
							 LPCSTR pszName, LPCSTR pszPassword,
							 IUser **ppOut)
{
	if (dwFlags & LUA_DIALOG) {
        if (!UseUserProfiles() || FAILED(VerifySupervisorPassword(szNULL))) {
            return InstallWizard(hwndOwner);
        }
        return ::DoUserDialog(hwndOwner, dwFlags, ppOut);
	}

	/* Null out return pointer for error cases. */
	if (ppOut != NULL)
		*ppOut = NULL;

	IUser *pUser;
	BOOL fReleaseMe = TRUE;

	HRESULT hres = GetUser(pszName, &pUser);
	if (SUCCEEDED(hres)) {
		hres = pUser->Authenticate(pszPassword);
		if (SUCCEEDED(hres)) {
			if ((dwFlags & LUA_SUPERVISORONLY) && (pUser->IsSupervisor() != S_OK)) {
				hres = E_ACCESSDENIED;
			}
			else if (ppOut != NULL) {
				*ppOut = pUser;
				fReleaseMe = FALSE;
			}
		}
		if (fReleaseMe)
			pUser->Release();
	}
	return hres;
}


STDMETHODIMP CLUDatabase::InstallComponent(REFCLSID clsidComponent,
										   LPCSTR pszName, DWORD dwFlags)
{
	return ResultFromScode(E_NOTIMPL);
}


STDMETHODIMP CLUDatabase::RemoveComponent(REFCLSID clsidComponent, LPCSTR pszName)
{
	return ResultFromScode(E_NOTIMPL);
}


#ifdef MSLOCUSR_USE_SUPERVISOR_PASSWORD

HRESULT IsCurrentUserSupervisor(IUserDatabase *pDB)
{
    IUser *pCurrentUser = NULL;

    HRESULT hres = pDB->GetCurrentUser(&pCurrentUser);
    if (SUCCEEDED(hres)) {
        hres = pCurrentUser->IsSupervisor();
    }
    if (pCurrentUser != NULL) {
        pCurrentUser->Release();
    }
    return hres;
}
#else
HRESULT IsCurrentUserSupervisor(IUserDatabase *pDB) { return S_OK; }
#endif
