#include "mslocusr.h" #include "msluglob.h" #include "profiles.h" #include #include 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