//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1993 - 1999. // // File: User.cpp // // Contents: implementation of CLogonUser // //---------------------------------------------------------------------------- #include "priv.h" #include "resource.h" #include "UserOM.h" #include // for NetUserSetInfo & structures #include // for NetApiBufferFree #include // for NERR_Success #include // for LsaOpenPolicy, etc. #include // for ConvertSidToStringSid #include "LogonIPC.h" #include "ProfileUtil.h" #include #include // for IsFolderPrivateForUser, SetFolderPermissionsForSharing #include // for WinStationEnumerate, etc. #include #include // PRQueryStatus, dpapi.lib typedef struct { SID sid; // contains 1 subauthority DWORD dwSubAuth; // 2nd subauthority } _ALIAS_SID; #define DECLARE_ALIAS_SID(rid) {{SID_REVISION,2,SECURITY_NT_AUTHORITY,{SECURITY_BUILTIN_DOMAIN_RID}},(rid)} struct { _ALIAS_SID sid; LPCWSTR szDefaultGroupName; LPCWSTR pwszActualGroupName; } g_groupname_map [] = { // in ascending order of privilege { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_GUESTS), L"Guests", NULL}, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_USERS), L"Users", NULL}, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_POWER_USERS), L"Power Users", NULL}, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_ADMINS), L"Administrators", NULL} }; void _InitializeGroupNames() { int i; for (i = 0; i < ARRAYSIZE(g_groupname_map); i++) { if (g_groupname_map[i].pwszActualGroupName == NULL) { WCHAR szGroupName[GNLEN + 1]; WCHAR szDomain[DNLEN + 1]; DWORD cchGroupName = ARRAYSIZE(szGroupName); DWORD cchDomain = ARRAYSIZE(szDomain); SID_NAME_USE eUse; szGroupName[0] = L'\0'; if (!LookupAccountSidW(NULL, &g_groupname_map[i].sid, szGroupName, &cchGroupName, szDomain, &cchDomain, &eUse)) { // if the lookup fails, fall back to the default StringCchCopy(szGroupName, ARRAYSIZE(szGroupName), g_groupname_map[i].szDefaultGroupName); } if (szGroupName[0] != L'\0') { LPWSTR pwsz; DWORD cch; cch = lstrlenW(szGroupName) + 1; pwsz = (LPWSTR)LocalAlloc(LPTR, cch * sizeof(WCHAR)); if (pwsz) { if (FAILED(StringCchCopy(pwsz, cch, szGroupName)) || InterlockedCompareExchangePointer((void**)&g_groupname_map[i].pwszActualGroupName, pwsz, NULL)) { // someone else beat us to initing pwszActualGroupName LocalFree(pwsz); pwsz = NULL; } } } } } } BOOL FreeGroupNames() { BOOL bRet = FALSE; int i; for (i = 0; i < ARRAYSIZE(g_groupname_map); i++) { LPWSTR psz = (LPWSTR)InterlockedExchangePointer((void **)&g_groupname_map[i].pwszActualGroupName, NULL); if (psz) { LocalFree(psz); bRet = TRUE; } } return bRet; } // // IUnknown Interface // ULONG CLogonUser::AddRef() { _cRef++; return _cRef; } ULONG CLogonUser::Release() { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) { return _cRef; } delete this; return 0; } HRESULT CLogonUser::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CLogonUser, IDispatch), QITABENT(CLogonUser, ILogonUser), {0}, }; return QISearch(this, qit, riid, ppvObj); } // // IDispatch Interface // STDMETHODIMP CLogonUser::GetTypeInfoCount(UINT* pctinfo) { return CIDispatchHelper::GetTypeInfoCount(pctinfo); } STDMETHODIMP CLogonUser::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { return CIDispatchHelper::GetTypeInfo(itinfo, lcid, pptinfo); } STDMETHODIMP CLogonUser::GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { return CIDispatchHelper::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); } STDMETHODIMP CLogonUser::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { return CIDispatchHelper::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); } // // ILogonUser Interface // STDMETHODIMP CLogonUser::get_setting(BSTR bstrName, VARIANT* pvarVal) { return _UserSettingAccessor(bstrName, pvarVal, FALSE); } STDMETHODIMP CLogonUser::put_setting(BSTR bstrName, VARIANT varVal) { return _UserSettingAccessor(bstrName, &varVal, TRUE); } STDMETHODIMP CLogonUser::get_isLoggedOn(VARIANT_BOOL* pbLoggedOn) { HRESULT hr = S_OK; CLogonIPC objLogon; if (NULL == pbLoggedOn) return E_POINTER; *pbLoggedOn = VARIANT_FALSE; if (objLogon.IsLogonServiceAvailable()) { *pbLoggedOn = ( objLogon.IsUserLoggedOn(_szLoginName, _szDomain) ) ? VARIANT_TRUE : VARIANT_FALSE; } else { TCHAR szUsername[UNLEN + 1]; DWORD cch = ARRAYSIZE(szUsername); if (GetUserName(szUsername, &cch) && (StrCmp(szUsername, _szLoginName) == 0)) { *pbLoggedOn = VARIANT_TRUE; } else { PLOGONID pSessions; DWORD cSessions; // Iterate the sessions looking for active and disconnected sessions only. // Then match the user name and domain (case INsensitive) for a result. if (WinStationEnumerate(SERVERNAME_CURRENT, &pSessions, &cSessions)) { PLOGONID pSession; DWORD i; for (i = 0, pSession = pSessions; i < cSessions; ++i, ++pSession) { if ((pSession->State == State_Active) || (pSession->State == State_Disconnected)) { WINSTATIONINFORMATION winStationInformation; DWORD cb; if (WinStationQueryInformation(SERVERNAME_CURRENT, pSession->SessionId, WinStationInformation, &winStationInformation, sizeof(winStationInformation), &cb)) { if ((0 == lstrcmpi(winStationInformation.UserName, _szLoginName)) && (0 == lstrcmpi(winStationInformation.Domain, _szDomain))) { *pbLoggedOn = VARIANT_TRUE; break; } } } } WinStationFreeMemory(pSessions); } else { DWORD dwErrorCode; dwErrorCode = GetLastError(); // We get RPC_S_INVALID_BINDING in safe mode, in which case // FUS is disabled, so we know the user isn't logged on. if (dwErrorCode != RPC_S_INVALID_BINDING) { hr = HRESULT_FROM_WIN32(dwErrorCode); } } } } return hr; } STDMETHODIMP CLogonUser::get_passwordRequired(VARIANT_BOOL* pbPasswordRequired) { CLogonIPC objLogon; if (objLogon.IsLogonServiceAvailable()) { *pbPasswordRequired = objLogon.TestBlankPassword(_szLoginName, _szDomain) ? VARIANT_FALSE: VARIANT_TRUE; } else { if (NULL == pbPasswordRequired) return E_POINTER; if ((BOOL)-1 == _bPasswordRequired) { BOOL fResult; HANDLE hToken; // Test for a blank password by trying to // logon the user with a blank password. fResult = LogonUser(_szLoginName, NULL, L"", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken); if (fResult != FALSE) { TBOOL(CloseHandle(hToken)); _bPasswordRequired = FALSE; } else { switch (GetLastError()) { case ERROR_ACCOUNT_RESTRICTION: // This means that blank password logons are disallowed, from // which we infer that the password is blank. _bPasswordRequired = FALSE; break; case ERROR_LOGON_TYPE_NOT_GRANTED: // Interactive logon was denied. We only get this if the // password is blank, otherwise we get ERROR_LOGON_FAILURE. _bPasswordRequired = FALSE; break; case ERROR_LOGON_FAILURE: // normal case (non-blank password) case ERROR_PASSWORD_MUST_CHANGE: // expired password _bPasswordRequired = TRUE; break; default: // We'll guess TRUE _bPasswordRequired = TRUE; break; } } } *pbPasswordRequired = (FALSE != _bPasswordRequired) ? VARIANT_TRUE : VARIANT_FALSE; } return S_OK; } STDMETHODIMP CLogonUser::get_interactiveLogonAllowed(VARIANT_BOOL *pbInteractiveLogonAllowed) { HRESULT hr; CLogonIPC objLogon; if (objLogon.IsLogonServiceAvailable()) { *pbInteractiveLogonAllowed = objLogon.TestInteractiveLogonAllowed(_szLoginName, _szDomain) ? VARIANT_TRUE : VARIANT_FALSE; hr = S_OK; } else { int iResult; iResult = ShellIsUserInteractiveLogonAllowed(_szLoginName); if (iResult == -1) { hr = E_ACCESSDENIED; } else { *pbInteractiveLogonAllowed = (iResult != 0) ? VARIANT_TRUE : VARIANT_FALSE; hr = S_OK; } } return hr; } HRESULT _IsGuestAccessMode(void) { HRESULT hr = E_FAIL; if (IsOS(OS_PERSONAL)) { hr = S_OK; } else if (IsOS(OS_PROFESSIONAL) && !IsOS(OS_DOMAINMEMBER)) { DWORD dwValue = 0; DWORD cbValue = sizeof(dwValue); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\LSA"), TEXT("ForceGuest"), NULL, &dwValue, &cbValue) && 1 == dwValue) { hr = S_OK; } } return hr; } STDMETHODIMP CLogonUser::get_isProfilePrivate(VARIANT_BOOL* pbPrivate) { HRESULT hr; if (NULL == pbPrivate) return E_POINTER; *pbPrivate = VARIANT_FALSE; // Only succeed if we are on Personal, or Professional with ForceGuest=1. hr = _IsGuestAccessMode(); if (SUCCEEDED(hr)) { // assume failure here hr = E_FAIL; _LookupUserSid(); if (NULL != _pszSID) { TCHAR szPath[MAX_PATH]; // Get the profile path DWORD cbData = sizeof(szPath); if (PathCombine(szPath, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"), _pszSID) && (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szPath, TEXT("ProfileImagePath"), NULL, szPath, &cbData))) { DWORD dwPrivateType; if (IsFolderPrivateForUser(szPath, _pszSID, &dwPrivateType, NULL)) { // Note that we return E_FAIL for FAT volumes if (0 == (dwPrivateType & IFPFU_NOT_NTFS)) { if (dwPrivateType & IFPFU_PRIVATE) { *pbPrivate = VARIANT_TRUE; } hr = S_OK; } } } } } return hr; } STDMETHODIMP CLogonUser::makeProfilePrivate(VARIANT_BOOL bPrivate) { HRESULT hr; // Only succeed if we are on Personal, or Professional with ForceGuest=1. hr = _IsGuestAccessMode(); if (SUCCEEDED(hr)) { // assume failure here hr = E_FAIL; _LookupUserSid(); if (NULL != _pszSID) { TCHAR szPath[MAX_PATH]; // Get the profile path DWORD cbData = sizeof(szPath); if (PathCombine(szPath, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"), _pszSID) && (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szPath, TEXT("ProfileImagePath"), NULL, szPath, &cbData))) { if (SetFolderPermissionsForSharing(szPath, _pszSID, (VARIANT_TRUE == bPrivate) ? 0 : 1, NULL)) { hr = S_OK; } } } } return hr; } STDMETHODIMP CLogonUser::logon(BSTR pbstrPassword, VARIANT_BOOL* pbRet) { HRESULT hr; CLogonIPC objLogon; TCHAR szPassword[PWLEN + 1]; if (pbstrPassword) { StringCchCopy(szPassword, ARRAYSIZE(szPassword), pbstrPassword); } else { szPassword[0] = TEXT('\0'); } if (!objLogon.IsLogonServiceAvailable()) { *pbRet = VARIANT_FALSE; hr = S_OK; } else { if (objLogon.LogUserOn(_szLoginName, _szDomain, szPassword)) { *pbRet = VARIANT_TRUE; hr = S_OK; } else { *pbRet = VARIANT_FALSE; hr = HRESULT_FROM_WIN32(GetLastError()); } } return hr; } STDMETHODIMP CLogonUser::logoff(VARIANT_BOOL* pbRet) { HRESULT hr; CLogonIPC objLogon; if (objLogon.IsLogonServiceAvailable()) { *pbRet = ( objLogon.LogUserOff(_szLoginName, _szDomain) ) ? VARIANT_TRUE : VARIANT_FALSE; } else { *pbRet = ( ExitWindowsEx(EWX_LOGOFF, 0) ) ? VARIANT_TRUE : VARIANT_FALSE; } hr = S_OK; return hr; } // Borrowed from msgina BOOL IsAutologonUser(LPCTSTR szUser, LPCTSTR szDomain) { BOOL fIsUser = FALSE; HKEY hkey = NULL; TCHAR szAutologonUser[UNLEN + sizeof('\0')]; TCHAR szAutologonDomain[DNLEN + sizeof('\0')]; TCHAR szTempDomainBuffer[DNLEN + sizeof('\0')]; DWORD cbBuffer; DWORD dwType; *szTempDomainBuffer = 0; // Domain may be a empty string. If this is the case... if (0 == *szDomain) { DWORD cchBuffer; // We really mean the local machine name // Point to our local buffer szDomain = szTempDomainBuffer; cchBuffer = ARRAYSIZE(szTempDomainBuffer); GetComputerName(szTempDomainBuffer, &cchBuffer); } // See if the domain and user name if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon"), 0, KEY_QUERY_VALUE, &hkey)) { // Check the user name cbBuffer = sizeof(szAutologonUser); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("DefaultUserName"), 0, &dwType, (LPBYTE)szAutologonUser, &cbBuffer)) { // Does it match? if (0 == lstrcmpi(szAutologonUser, szUser)) { // Yes. Now check domain cbBuffer = sizeof(szAutologonDomain); if (ERROR_SUCCESS == RegQueryValueEx(hkey, TEXT("DefaultDomainName"), 0, &dwType, (LPBYTE)szAutologonDomain, &cbBuffer)) { // Make sure domain matches if (0 == lstrcmpi(szAutologonDomain, szDomain)) { // Success - the users match fIsUser = TRUE; } } } } RegCloseKey(hkey); } return fIsUser; } // Borrowed from msgina NTSTATUS SetAutologonPassword(LPCWSTR szPassword) { NTSTATUS Status = STATUS_SUCCESS; OBJECT_ATTRIBUTES ObjectAttributes; LSA_HANDLE LsaHandle = NULL; InitializeObjectAttributes(&ObjectAttributes, NULL, 0L, (HANDLE)NULL, NULL); Status = LsaOpenPolicy(NULL, &ObjectAttributes, POLICY_CREATE_SECRET, &LsaHandle); if (NT_SUCCESS(Status)) { UNICODE_STRING SecretName; Status = RtlInitUnicodeStringEx(&SecretName, L"DefaultPassword"); if (NT_SUCCESS(Status)) { UNICODE_STRING SecretValue; Status = RtlInitUnicodeStringEx(&SecretValue, szPassword); if (NT_SUCCESS(Status)) { Status = LsaStorePrivateData(LsaHandle, &SecretName, &SecretValue); } } LsaClose(LsaHandle); } return Status; } STDMETHODIMP CLogonUser::changePassword(VARIANT varNewPassword, VARIANT varOldPassword, VARIANT_BOOL* pbRet) { HRESULT hr; if (VT_BSTR == varNewPassword.vt && VT_BSTR == varOldPassword.vt) { TCHAR szUsername[UNLEN + sizeof('\0')]; DWORD cch = ARRAYSIZE(szUsername); NET_API_STATUS nasRet; USER_MODALS_INFO_0 *pumi0 = NULL; LPWSTR pszNewPassword = varNewPassword.bstrVal ? varNewPassword.bstrVal : L"\0"; // We used to create accounts with UF_PASSWD_NOTREQD, and we still do // when password policy is enabled. If UF_PASSWD_NOTREQD is set, then // the below code will succeed even with password policy is enabled, // so do a minimal policy check here. nasRet = NetUserModalsGet(NULL, 0, (LPBYTE*)&pumi0); if (nasRet == NERR_Success && pumi0 != NULL) { if ((DWORD)lstrlen(pszNewPassword) < pumi0->usrmod0_min_passwd_len) { nasRet = NERR_PasswordTooShort; } NetApiBufferFree(pumi0); } if (nasRet == NERR_Success) { if (GetUserName(szUsername, &cch) && (StrCmp(szUsername, _szLoginName) == 0)) { // This is the case of a user changing their own password. // Both passwords must be provided to effect the change. LPCWSTR pszOldPassword = varOldPassword.bstrVal ? varOldPassword.bstrVal : L"\0"; nasRet = NetUserChangePassword(NULL, // Local machine _szLoginName, // name of the person to change pszOldPassword, // old password pszNewPassword); // new password } else { // This is the case of an admin changing someone else's password. // As an administrator they don't need to enter the old password. USER_INFO_1003 usri1003 = { pszNewPassword }; nasRet = NetUserSetInfo(NULL, // local machine _szLoginName, // name of the person to change 1003, // structure level (LPBYTE)&usri1003, // the update info NULL); // don't care } if (nasRet == NERR_Success) { // If this is the default user for autologon, delete the cleartext // password from the registry and save the new password. if (IsAutologonUser(_szLoginName, _szDomain)) { SHDeleteValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon"), TEXT("DefaultPassword")); SetAutologonPassword(pszNewPassword); } // Make an attempt to remove UF_PASSWD_NOTREQD if it's // currently set. Ignore errors since we already changed // the password above. USER_INFO_1008 *pusri1008; if (NERR_Success == NetUserGetInfo(NULL, _szLoginName, 1008, (LPBYTE*)&pusri1008)) { if (pusri1008->usri1008_flags & UF_PASSWD_NOTREQD) { pusri1008->usri1008_flags &= ~UF_PASSWD_NOTREQD; NetUserSetInfo(NULL, _szLoginName, 1008, (LPBYTE)pusri1008, NULL); } NetApiBufferFree(pusri1008); } } } hr = HRESULT_FROM_WIN32(nasRet); if (SUCCEEDED(hr)) { _bPasswordRequired = !(L'\0' == *pszNewPassword); } } else { hr = E_INVALIDARG; } *pbRet = ( SUCCEEDED(hr) ) ? VARIANT_TRUE : VARIANT_FALSE; return hr; } STDAPI CLogonUser_Create(REFIID riid, void** ppvObj) { return CLogonUser::Create(TEXT(""), TEXT(""), TEXT(""), riid, ppvObj); } HRESULT CLogonUser::Create(LPCTSTR pszLoginName, LPCTSTR pszFullName, LPCTSTR pszDomain, REFIID riid, LPVOID* ppv) { HRESULT hr = E_OUTOFMEMORY; CLogonUser* pUser = new CLogonUser(pszLoginName, pszFullName, pszDomain); if (pUser) { hr = pUser->QueryInterface(riid, ppv); pUser->Release(); } return hr; } CLogonUser::CLogonUser(LPCTSTR pszLoginName, LPCTSTR pszFullName, LPCTSTR pszDomain) : _cRef(1), CIDispatchHelper(&IID_ILogonUser, &LIBID_SHGINALib), _strDisplayName(NULL), _strPictureSource(NULL), _strDescription(NULL), _strHint(NULL), _iPrivilegeLevel(-1), _pszSID(NULL), _bPasswordRequired((BOOL)-1) { _InitializeGroupNames(); StringCchCopy(_szLoginName, ARRAYSIZE(_szLoginName), pszLoginName); StringCchCopy(_szDomain, ARRAYSIZE(_szDomain), pszDomain); if (pszFullName) { _strDisplayName = SysAllocString(pszFullName); } // Use the EOF marker to indicate an uninitialized string _szPicture[0] = _TEOF; DllAddRef(); } CLogonUser::~CLogonUser() { SysFreeString(_strDisplayName); SysFreeString(_strPictureSource); SysFreeString(_strDescription); SysFreeString(_strHint); if (_pszSID) { LocalFree(_pszSID); } ASSERT(_cRef == 0); DllRelease(); } typedef HRESULT (CLogonUser::*PFNPUT)(VARIANT); typedef HRESULT (CLogonUser::*PFNGET)(VARIANT *); struct SETTINGMAP { LPCWSTR szSetting; PFNGET pfnGet; PFNPUT pfnPut; }; #define MAP_SETTING(x) { L#x, CLogonUser::_Get##x, CLogonUser::_Put##x } #define MAP_SETTING_GET_ONLY(x) { L#x, CLogonUser::_Get##x, NULL } #define MAP_SETTING_PUT_ONLY(x) { L#x, NULL, CLogonUser::_Put##x } // _UserSettingAccessor // // bstrName - name of the setting you widh to access // pvarVal - the value of the named setting // bPut - if true the named setting will be updated // with the value pointed to by pvarVal // if false the named setting will be retrieved // in pvarVal // HRESULT CLogonUser::_UserSettingAccessor(BSTR bstrName, VARIANT *pvarVal, BOOL bPut) { static const SETTINGMAP setting_map[] = { // in descending order of expected access frequecy MAP_SETTING(LoginName), MAP_SETTING(DisplayName), MAP_SETTING(Picture), MAP_SETTING_GET_ONLY(PictureSource), MAP_SETTING(AccountType), MAP_SETTING(Hint), MAP_SETTING_GET_ONLY(Domain), MAP_SETTING(Description), MAP_SETTING_GET_ONLY(SID), MAP_SETTING_GET_ONLY(UnreadMail) }; HRESULT hr; INT i; // start off assuming bogus setting name hr = E_INVALIDARG; for ( i = 0; i < ARRAYSIZE(setting_map); i++) { if (StrCmpW(bstrName, setting_map[i].szSetting) == 0) { // what do we want to do with the named setting ... if ( bPut ) { // ... change its value PFNPUT pfnPut = setting_map[i].pfnPut; if ( pfnPut != NULL ) { hr = (this->*pfnPut)(*pvarVal); } else { // we don't support updated the value for this setting hr = E_FAIL; } } else { // ... retrieve its value PFNGET pfnGet = setting_map[i].pfnGet; if ( pfnGet != NULL ) { hr = (this->*pfnGet)(pvarVal); } else { // we don't support retieving the value for this setting hr = E_FAIL; } } break; } } return hr; } HRESULT CLogonUser::_GetDisplayName(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; if (NULL == _strDisplayName) { PUSER_INFO_1011 pusri1011 = NULL; NET_API_STATUS nasRet; nasRet = NetUserGetInfo(NULL, // local machine _szLoginName, // whose information do we want 1011, // structure level (LPBYTE*)&pusri1011); // pointer to the structure we'll receive if ( nasRet == NERR_Success ) { _strDisplayName = SysAllocString(pusri1011->usri1011_full_name); NetApiBufferFree(pusri1011); } } pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_strDisplayName); return S_OK; } HRESULT CLogonUser::_PutDisplayName(VARIANT var) { HRESULT hr; if ( var.vt == VT_BSTR ) { USER_INFO_1011 usri1011; NET_API_STATUS nasRet; if ( var.bstrVal ) { usri1011.usri1011_full_name = var.bstrVal; } else { // OK to have emply string as display name usri1011.usri1011_full_name = L"\0"; } nasRet = NetUserSetInfo(NULL, // local machine _szLoginName, // name of the person to change 1011, // structure level (LPBYTE)&usri1011, // the update info NULL); // don't care if ( nasRet == NERR_Success ) { // DisplayName was successfully changed. Remember to update our // local copy SysFreeString(_strDisplayName); _strDisplayName = SysAllocString(usri1011.usri1011_full_name); // Notify everyone that a user name has changed SHChangeDWORDAsIDList dwidl; dwidl.cb = SIZEOF(dwidl) - SIZEOF(dwidl.cbZero); dwidl.dwItem1 = SHCNEE_USERINFOCHANGED; dwidl.dwItem2 = 0; dwidl.cbZero = 0; SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, (LPCITEMIDLIST)&dwidl, NULL); hr = S_OK; } else { // insufficient privileges? hr = HRESULT_FROM_WIN32(nasRet); } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_GetLoginName(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_szLoginName); return S_OK; } HRESULT CLogonUser::_PutLoginName(VARIANT var) { HRESULT hr; if ( (var.vt == VT_BSTR) && (var.bstrVal) && (*var.bstrVal) ) { if (_szLoginName[0] == TEXT('\0')) { // We haven't been initialized yet. Initialize to the given name. hr = StringCchCopy(_szLoginName, ARRAYSIZE(_szLoginName), var.bstrVal); } else { USER_INFO_0 usri0; NET_API_STATUS nasRet; usri0.usri0_name = var.bstrVal; nasRet = NetUserSetInfo(NULL, // local machine _szLoginName, // name of the person to change 0, // structure level (LPBYTE)&usri0, // the update info NULL); // don't care if (nasRet == NERR_Success) { // We should also rename the user's picture file to match // their new LoginName if (_TEOF == _szPicture[0]) { // This requires _szLoginName to still have the old name, // so do it before updating _szLoginName below. hr = _InitPicture(); } else { hr = S_OK; } if (SUCCEEDED(hr) && (TEXT('\0') != _szPicture[0])) { LPTSTR pszOldPicturePath; TCHAR szNewPicture[ARRAYSIZE(_szPicture)]; pszOldPicturePath = &_szPicture[7]; // &[7] to skip over the "file://" part if (SUCCEEDED(StringCchCopy(szNewPicture, ARRAYSIZE(szNewPicture), pszOldPicturePath)) && PathRemoveFileSpec(szNewPicture) && PathAppend(szNewPicture, usri0.usri0_name) && SUCCEEDED(StringCchCat(szNewPicture, ARRAYSIZE(szNewPicture), PathFindExtension(pszOldPicturePath)))) { if (MoveFileEx(pszOldPicturePath, szNewPicture, MOVEFILE_REPLACE_EXISTING)) { StringCchCopy(_szPicture, ARRAYSIZE(_szPicture), TEXT("file://")); StringCchCat(_szPicture, ARRAYSIZE(_szPicture), szNewPicture); } else { // Give up and just try to delete the old picture // (otherwise it will be abandoned). DeleteFile(pszOldPicturePath); _szPicture[0] = _TEOF; } } } // LoginName was successfully changed. Remember to update our local copy hr = StringCchCopy(_szLoginName, ARRAYSIZE(_szLoginName), usri0.usri0_name); } else { // insufficient privileges? hr = HRESULT_FROM_WIN32(nasRet); } } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_GetDomain(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_szDomain); return S_OK; } HRESULT CLogonUser::_GetPicture(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; if (_TEOF == _szPicture[0]) { _InitPicture(); } pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_szPicture); return S_OK; } HRESULT CLogonUser::_PutPicture(VARIANT var) { HRESULT hr; if ((var.vt == VT_BSTR) && (var.bstrVal) && (*var.bstrVal)) { // Passed a string which is not NULL and not empty TCHAR szNewPicturePath[MAX_PATH]; DWORD dwSize = ARRAYSIZE(szNewPicturePath); // get the path of the image we want to copy if (PathIsURL(var.bstrVal)) { hr = PathCreateFromUrl(var.bstrVal, szNewPicturePath, &dwSize, NULL); } else { hr = StringCchCopy(szNewPicturePath, ARRAYSIZE(szNewPicturePath), var.bstrVal); } if (SUCCEEDED(hr)) { // REVIEW (phellar) : we build the URL string ourself so we know it's of the form, // file://, the path starts on the 7th character. if ( _TEOF == _szPicture[0] || (StrCmpI(szNewPicturePath, &_szPicture[7]) != 0)) { hr = _SetPicture(szNewPicturePath); } } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_GetPictureSource(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; if (NULL == _strPictureSource) { TCHAR szHintKey[MAX_PATH]; DWORD dwType = REG_SZ; DWORD dwSize = 0; if (PathCombine(szHintKey, c_szRegRoot, _szLoginName) && (SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, c_szPictureSrcVal, &dwType, NULL, &dwSize) == ERROR_SUCCESS) && (REG_SZ == dwType) && (dwSize > 0)) { _strPictureSource = SysAllocStringLen(NULL, dwSize / sizeof(TCHAR)); if (NULL != _strPictureSource) { if (ERROR_SUCCESS != SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, c_szPictureSrcVal, NULL, (LPVOID)_strPictureSource, &dwSize)) { SysFreeString(_strPictureSource); _strPictureSource = NULL; } } } } pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_strPictureSource); return S_OK; } HRESULT CLogonUser::_GetDescription(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; if (NULL == _strDescription) { NET_API_STATUS nasRet; USER_INFO_1007 *pusri1007; nasRet = NetUserGetInfo(NULL, // local machine _szLoginName, // whose information do we want 1007, // structure level (LPBYTE*)&pusri1007); // pointer to the structure we'll receive if ( nasRet == NERR_Success ) { _strDescription = SysAllocString(pusri1007->usri1007_comment); NetApiBufferFree(pusri1007); } } pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_strDescription); return S_OK; } HRESULT CLogonUser::_PutDescription(VARIANT var) { HRESULT hr; if ( var.vt == VT_BSTR ) { USER_INFO_1007 usri1007; NET_API_STATUS nasRet; if ( var.bstrVal ) { usri1007.usri1007_comment = var.bstrVal; } else { // OK to have emply string as a description usri1007.usri1007_comment = L"\0"; } nasRet = NetUserSetInfo(NULL, // local machine _szLoginName, // name of the person to change 1007, // structure level (LPBYTE)&usri1007, // the update info NULL); // don't care if ( nasRet == NERR_Success ) { // Description was successfully changed. Remember to update our // local copy SysFreeString(_strDescription); _strDescription = SysAllocString(usri1007.usri1007_comment); hr = S_OK; } else { // insufficient privileges? hr = HRESULT_FROM_WIN32(nasRet); } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_GetHint(VARIANT* pvar) { if (NULL == pvar) return E_POINTER; if (NULL == _strHint) { TCHAR szHintKey[MAX_PATH]; DWORD dwType = REG_SZ; DWORD dwSize = 0; if (PathCombine(szHintKey, c_szRegRoot, _szLoginName) && (SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, NULL, &dwType, NULL, &dwSize) == ERROR_SUCCESS) && (REG_SZ == dwType) && (dwSize > 0) && (dwSize < 512)) { _strHint = SysAllocStringLen(NULL, dwSize/sizeof(TCHAR)); if (NULL != _strHint) { if (ERROR_SUCCESS != SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, NULL, NULL, (LPVOID)_strHint, &dwSize)) { SysFreeString(_strHint); _strHint = NULL; } } } } pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_strHint); return S_OK; } HRESULT CLogonUser::_PutHint(VARIANT var) { HRESULT hr; if ( var.vt == VT_BSTR ) { DWORD dwErr; TCHAR *pszHint; HKEY hkUserHint; if (var.bstrVal) { pszHint = var.bstrVal; } else { pszHint = TEXT("\0"); } dwErr = _OpenUserHintKey(KEY_SET_VALUE, &hkUserHint); if ( dwErr == ERROR_SUCCESS ) { DWORD cbData = lstrlen(pszHint) * sizeof(TCHAR) + sizeof(TEXT('\0')); dwErr = RegSetValueEx(hkUserHint, NULL, 0, REG_SZ, (LPBYTE)pszHint, cbData); RegCloseKey(hkUserHint); } if ( dwErr == ERROR_SUCCESS ) { // Hint was successfully changed. Remember to update our local copy SysFreeString(_strHint); _strHint = SysAllocString(pszHint); hr = S_OK; } else { hr = HRESULT_FROM_WIN32(dwErr); } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_GetAccountType(VARIANT* pvar) { HRESULT hr; hr = E_FAIL; if (pvar) { if (-1 == _iPrivilegeLevel) { NET_API_STATUS nasRet; PLOCALGROUP_INFO_0 plgi0; DWORD dwEntriesRead; DWORD dwEntriesTotal; nasRet = NetUserGetLocalGroups( NULL, _szLoginName, 0, 0, (LPBYTE *)&plgi0, MAX_PREFERRED_LENGTH, &dwEntriesRead, &dwEntriesTotal); if ( nasRet == NERR_Success ) { // make sure we read all the groups ASSERT(dwEntriesRead == dwEntriesTotal) INT i, j, iMostPrivileged; for (i = 0, iMostPrivileged = 0; i < (INT)dwEntriesRead; i++) { for (j = ARRAYSIZE(g_groupname_map)-1; j > 0; j--) { if (lstrcmpiW(plgi0[i].lgrpi0_name, g_groupname_map[j].pwszActualGroupName) == 0) { break; } } iMostPrivileged = (iMostPrivileged > j) ? iMostPrivileged : j; } _iPrivilegeLevel = iMostPrivileged; nasRet = NetApiBufferFree((LPVOID)plgi0); } hr = HRESULT_FROM_WIN32(nasRet); } if (-1 != _iPrivilegeLevel) { pvar->vt = VT_I4; pvar->lVal = _iPrivilegeLevel; hr = S_OK; } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_PutAccountType(VARIANT var) { HRESULT hr; hr = VariantChangeType(&var, &var, 0, VT_I4); if (SUCCEEDED(hr)) { if (var.lVal < 0 || var.lVal >= ARRAYSIZE(g_groupname_map)) { hr = E_INVALIDARG; } else if (var.lVal != _iPrivilegeLevel) { NET_API_STATUS nasRet; TCHAR szDomainAndName[256]; LOCALGROUP_MEMBERS_INFO_3 lgrmi3; // First add the user to their new group hr = StringCchPrintf(szDomainAndName, ARRAYSIZE(szDomainAndName), TEXT("%s\\%s"), _szDomain, _szLoginName); if (SUCCEEDED(hr)) { lgrmi3.lgrmi3_domainandname = szDomainAndName; nasRet = NetLocalGroupAddMembers(NULL, g_groupname_map[var.lVal].pwszActualGroupName, 3, (LPBYTE)&lgrmi3, 1); // If we were successful in adding to the group or // they were already in the group ... if ((nasRet == NERR_Success) || (nasRet == ERROR_MEMBER_IN_ALIAS)) { // remember the new privilege level _iPrivilegeLevel = var.lVal; // remove them from all more-privileged groups for (int i = var.lVal+1; i < ARRAYSIZE(g_groupname_map); i++) { // "Power Users" doesn't exist on Personal, so this will // fail sometimes. NetLocalGroupDelMembers(NULL, g_groupname_map[i].pwszActualGroupName, 3, (LPBYTE)&lgrmi3, 1); } } else { hr = HRESULT_FROM_WIN32(nasRet); } } } } return hr; } HRESULT CLogonUser::_LookupUserSid() { HRESULT hr; if (NULL == _pszSID) { BYTE rgSidBuffer[sizeof(SID) + (SID_MAX_SUB_AUTHORITIES-1)*sizeof(ULONG)]; PSID pSid = (PSID)rgSidBuffer; DWORD cbSid = sizeof(rgSidBuffer); TCHAR szDomainName[MAX_PATH]; DWORD cbDomainName = ARRAYSIZE(szDomainName); SID_NAME_USE snu; if (LookupAccountName( (TEXT('\0') != _szDomain[0]) ? _szDomain : NULL, _szLoginName, pSid, &cbSid, szDomainName, &cbDomainName, &snu)) { ConvertSidToStringSid(pSid, &_pszSID); } } if (NULL == _pszSID) { DWORD dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); } else { hr = S_OK; } return hr; } HRESULT CLogonUser::_GetSID(VARIANT* pvar) { HRESULT hr; if (pvar) { hr = _LookupUserSid(); if (NULL != _pszSID) { pvar->vt = VT_BSTR; pvar->bstrVal = SysAllocString(_pszSID); hr = pvar->bstrVal ? S_OK : E_OUTOFMEMORY; } } else { hr = E_INVALIDARG; } return hr; } DWORD CLogonUser::_GetExpiryDays (HKEY hKeyCurrentUser) { DWORD dwDays; DWORD dwDataType; DWORD dwData; DWORD dwDataSize; HKEY hKey; static const TCHAR s_szBaseKeyName[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\UnreadMail"); static const TCHAR s_szMessageExpiryValueName[] = TEXT("MessageExpiryDays"); dwDays = 3; if (RegOpenKeyEx(hKeyCurrentUser, s_szBaseKeyName, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { dwDataSize = sizeof(dwData); if ((RegQueryValueEx(hKey, s_szMessageExpiryValueName, NULL, &dwDataType, (LPBYTE)&dwData, &dwDataSize) == ERROR_SUCCESS) && (dwDataType == REG_DWORD) && (dwData <= 30)) { dwDays = dwData; } TBOOL(RegCloseKey(hKey)); } if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, s_szBaseKeyName, 0, KEY_QUERY_VALUE, &hKey)) { dwDataSize = sizeof(dwData); if ((RegQueryValueEx(hKey, s_szMessageExpiryValueName, NULL, &dwDataType, (LPBYTE)&dwData, &dwDataSize) == ERROR_SUCCESS) && (dwDataType == REG_DWORD) && (dwData <= 30)) { dwDays = dwData; } TBOOL(RegCloseKey(hKey)); } return dwDays; } STDMETHODIMP CLogonUser::getMailAccountInfo(UINT uiAccountIndex, VARIANT *pvarAccountName, UINT *pcUnreadMessages) { HRESULT hr; DWORD dwComputerNameSize; TCHAR szComputerName[CNLEN + sizeof('\0')]; hr = E_FAIL; // Only do this for local computer accounts. dwComputerNameSize = ARRAYSIZE(szComputerName); if ((GetComputerName(szComputerName, &dwComputerNameSize) != FALSE) && (lstrcmpi(szComputerName, _szDomain) == 0)) { CUserProfile profile(_szLoginName, _szDomain); if (static_cast(profile) != NULL) { DWORD dwCount; TCHAR szMailAccountName[100]; hr = SHEnumerateUnreadMailAccounts(profile, uiAccountIndex, szMailAccountName, ARRAYSIZE(szMailAccountName)); if (SUCCEEDED(hr)) { if (pvarAccountName) { pvarAccountName->vt = VT_BSTR; pvarAccountName->bstrVal = SysAllocString(szMailAccountName); hr = pvarAccountName->bstrVal ? S_OK : E_OUTOFMEMORY; } if (SUCCEEDED(hr) && pcUnreadMessages) { FILETIME ft, ftCurrent; SYSTEMTIME st; BOOL ftExpired = false; DWORD dwExpiryDays = _GetExpiryDays(profile); hr = SHGetUnreadMailCount(profile, szMailAccountName, &dwCount, &ft, NULL, 0); IncrementFILETIME(&ft, FT_ONEDAY * dwExpiryDays); GetLocalTime(&st); SystemTimeToFileTime(&st, &ftCurrent); ftExpired = ((CompareFileTime(&ft, &ftCurrent) < 0) || (dwExpiryDays == 0)); if (SUCCEEDED(hr) && !ftExpired) { *pcUnreadMessages = dwCount; } else { *pcUnreadMessages = 0; } } } } } return hr; } HRESULT CLogonUser::_GetUnreadMail(VARIANT* pvar) { HRESULT hr; if (pvar) { DWORD dwComputerNameSize; TCHAR szComputerName[CNLEN + sizeof('\0')]; hr = E_FAIL; // Only do this for local computer accounts. dwComputerNameSize = ARRAYSIZE(szComputerName); if ((GetComputerName(szComputerName, &dwComputerNameSize) != FALSE) && (lstrcmpi(szComputerName, _szDomain) == 0)) { CUserProfile profile(_szLoginName, _szDomain); if (static_cast(profile) != NULL) { DWORD dwCount; FILETIME ftFilter; SYSTEMTIME st; DWORD dwExpiryDays = _GetExpiryDays(profile); GetLocalTime(&st); SystemTimeToFileTime(&st, &ftFilter); DecrementFILETIME(&ftFilter, FT_ONEDAY * dwExpiryDays); hr = SHGetUnreadMailCount(profile, NULL, &dwCount, &ftFilter, NULL, 0); if (SUCCEEDED(hr) && (dwExpiryDays != 0)) { pvar->vt = VT_UI4; pvar->uintVal = dwCount; } } } } else { hr = E_INVALIDARG; } return hr; } HRESULT CLogonUser::_InitPicture() { HRESULT hr; hr = StringCchCopy(_szPicture, ARRAYSIZE(_szPicture), TEXT("file://")); if (SUCCEEDED(hr)) { TCHAR szTemp[MAX_PATH]; hr = SHGetUserPicturePath(_szLoginName, SHGUPP_FLAG_CREATE, szTemp); if (SUCCEEDED(hr)) { hr = StringCchCat(_szPicture, ARRAYSIZE(_szPicture), szTemp); } } if (FAILED(hr)) { _szPicture[0] = TEXT('\0'); } return hr; } HRESULT CLogonUser::_SetPicture(LPCTSTR pszNewPicturePath) { // use shell32!SHSetUserPicturePath to set the user's // picture path. If this is successful then update the // _szPicture member variable. HRESULT hr = SHSetUserPicturePath(_szLoginName, 0, pszNewPicturePath); if ( S_OK == hr ) { DWORD dwErr; HKEY hkUserHint; SysFreeString(_strPictureSource); _strPictureSource = SysAllocString(pszNewPicturePath); dwErr = _OpenUserHintKey(KEY_SET_VALUE, &hkUserHint); if ( dwErr == ERROR_SUCCESS ) { if (pszNewPicturePath) { DWORD cbData = lstrlen(pszNewPicturePath) * sizeof(TCHAR) + sizeof(TEXT('\0')); dwErr = RegSetValueEx(hkUserHint, c_szPictureSrcVal, 0, REG_SZ, (LPBYTE)pszNewPicturePath, cbData); } else { dwErr = RegDeleteValue(hkUserHint, c_szPictureSrcVal); } RegCloseKey(hkUserHint); } hr = StringCchCopy(_szPicture, ARRAYSIZE(_szPicture), TEXT("file://")); if (SUCCEEDED(hr)) { TCHAR szTemp[MAX_PATH]; hr = SHGetUserPicturePath(_szLoginName, 0, szTemp); if (SUCCEEDED(hr)) { hr = StringCchCat(_szPicture, ARRAYSIZE(_szPicture), szTemp); } } if (FAILED(hr)) { hr = StringCchCopy(_szPicture, ARRAYSIZE(_szPicture), pszNewPicturePath); } } return hr; } DWORD CLogonUser::_OpenUserHintKey(REGSAM sam, HKEY *phkey) { DWORD dwErr; TCHAR szHintKey[MAX_PATH]; // We have to store hint information under HKLM so the logon page can // access it, Also, we want to allow non-admins to change their own // hints, but non-admins can't write values under HKLM by default. // // The solution is to use subkeys rather than named values so we can // tweak the ACLs on a per-user basis. // // A non-admin user needs the ability to do 2 things: // 1. Create a hint subkey for themselves if one does not exist. // 2. Modify the hint contained in their subkey if one already exists. // // At install time, we set the ACL on the Hints key to allow // Authenticated Users KEY_CREATE_SUB_KEY access. Thus, a user is // able to create a hint for themselves if one doesn't exist. // // Immediately after creating a hint subkey, whether it was created // by the target user or an admin, we grant the target user // KEY_SET_VALUE access to the subkey. This ensures that a user can // modify their own hint no matter who created it for them. // // Note that we don't call RegCreateKeyEx or SHSetValue since we // don't want the key to be automatically created here. // // Note that admins are able to create and modify hints for any user, // but a non-admin is only able to create or modify their own hint. // First assume the hint already exists and just try to open it. if (PathCombine(szHintKey, c_szRegRoot, _szLoginName)) { dwErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szHintKey, 0, sam, phkey); if ( dwErr == ERROR_FILE_NOT_FOUND ) { HKEY hkHints; // The hint subkey doesn't exist yet for this user. // Try to create one. // Open the Hints key for KEY_CREATE_SUB_KEY dwErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegRoot, 0, KEY_CREATE_SUB_KEY, &hkHints); if (dwErr == ERROR_SUCCESS) { // Create a subkey for this user dwErr = RegCreateKeyEx(hkHints, _szLoginName, 0, NULL, 0, sam, NULL, phkey, NULL); if (dwErr == ERROR_SUCCESS) { // Grant KEY_SET_VALUE access to the user so they can // change their own hint. _LookupUserSid(); if (NULL != _pszSID) { TCHAR szKey[MAX_PATH]; TCHAR szSD[MAX_PATH]; if (PathCombine(szKey, TEXT("MACHINE"), szHintKey) && SUCCEEDED(StringCchPrintf(szSD, ARRAYSIZE(szSD), TEXT("D:(A;;0x2;;;%s)"), // 0x2 = KEY_SET_VALUE _pszSID))) { if (!SetDacl(szKey, SE_REGISTRY_KEY, szSD)) { dwErr = GetLastError(); } } else { dwErr = ERROR_INSUFFICIENT_BUFFER; } } else { dwErr = ERROR_NOT_ENOUGH_MEMORY; } } RegCloseKey(hkHints); } } } else { dwErr = ERROR_INSUFFICIENT_BUFFER; } return dwErr; } STDMETHODIMP CLogonUser::get_isPasswordResetAvailable(VARIANT_BOOL* pbResetAvailable) { DWORD dwResult; if (!pbResetAvailable) return E_POINTER; *pbResetAvailable = VARIANT_FALSE; if (0 == PRQueryStatus(NULL, _szLoginName, &dwResult)) { if (0 == dwResult) { *pbResetAvailable = VARIANT_TRUE; } } return S_OK; }