|
|
//+---------------------------------------------------------------------------
//
// 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 <lmaccess.h> // for NetUserSetInfo & structures
#include <lmapibuf.h> // for NetApiBufferFree
#include <lmerr.h> // for NERR_Success
#include <ntlsa.h> // for LsaOpenPolicy, etc.
#include <sddl.h> // for ConvertSidToStringSid
#include <tchar.h> // for _TEOF
#include "LogonIPC.h"
#include "ProfileUtil.h"
#include <MSGinaExports.h>
#include <msshrui.h> // for IsFolderPrivateForUser, SetFolderPermissionsForSharing
#include <winsta.h> // for WinStationEnumerate, etc.
#include <ccstock.h>
#include <passrec.h> // 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; WCHAR szActualGroupName[GNLEN + sizeof('\0')]; } g_groupname_map [] = { // in ascending order of privilege
{ DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_GUESTS), L"Guests", L"" }, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_USERS), L"Users", L"" }, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_POWER_USERS), L"Power Users", L"" }, { DECLARE_ALIAS_SID(DOMAIN_ALIAS_RID_ADMINS), L"Administrators", L"" } };
void _InitializeGroupNames() { int i;
for (i = 0; i < ARRAYSIZE(g_groupname_map); i++) { WCHAR szDomain[DNLEN + sizeof('\0')]; DWORD dwNameSize = ARRAYSIZE(g_groupname_map[i].szActualGroupName); DWORD dwDomainSize = ARRAYSIZE(szDomain); SID_NAME_USE eUse;
if (L'\0' == g_groupname_map[i].szActualGroupName[0] && !LookupAccountSidW(NULL, &g_groupname_map[i].sid, g_groupname_map[i].szActualGroupName, &dwNameSize, szDomain, &dwDomainSize, &eUse)) { lstrcpynW(g_groupname_map[i].szActualGroupName, g_groupname_map[i].szDefaultGroupName, ARRAYSIZE(g_groupname_map[i].szActualGroupName)); } } }
//
// 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 + sizeof('\0')]; DWORD cch = ARRAYSIZE(szUsername);
if (GetUserName(szUsername, &cch) && !StrCmp(szUsername, _szLoginName)) { *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; }
HMODULE g_hmodNTShrUI = NULL; static PFNISFOLDERPRIVATEFORUSER g_pfnIsFolderPrivateForUser = NULL; static PFNSETFOLDERPERMISSIONSFORSHARING g_pfnSetFolderPermissionsForSharing = NULL;
void _LoadNTShrUI(void) { if (NULL == g_hmodNTShrUI) { g_hmodNTShrUI = LoadLibraryW(L"ntshrui.dll");
if (NULL != g_hmodNTShrUI) { g_pfnIsFolderPrivateForUser = (PFNISFOLDERPRIVATEFORUSER)GetProcAddress(g_hmodNTShrUI, "IsFolderPrivateForUser"); g_pfnSetFolderPermissionsForSharing = (PFNSETFOLDERPERMISSIONSFORSHARING)GetProcAddress(g_hmodNTShrUI, "SetFolderPermissionsForSharing"); } } }
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
PathCombine(szPath, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"), _pszSID); DWORD cbData = sizeof(szPath); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szPath, TEXT("ProfileImagePath"), NULL, szPath, &cbData)) { DWORD dwPrivateType;
_LoadNTShrUI();
if (NULL != g_pfnIsFolderPrivateForUser && g_pfnIsFolderPrivateForUser(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
PathCombine(szPath, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"), _pszSID); DWORD cbData = sizeof(szPath); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szPath, TEXT("ProfileImagePath"), NULL, szPath, &cbData)) { _LoadNTShrUI();
if (NULL != g_pfnSetFolderPermissionsForSharing && g_pfnSetFolderPermissionsForSharing(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 + sizeof('\0')];
if (pbstrPassword) lstrcpyn(szPassword, pbstrPassword, ARRAYSIZE(szPassword)); else szPassword[0] = 0; if (!objLogon.IsLogonServiceAvailable()) { *pbRet = VARIANT_FALSE; return S_OK; }
if (objLogon.LogUserOn (_szLoginName, _szDomain, szPassword)) *pbRet = VARIANT_TRUE; else *pbRet = VARIANT_FALSE;
if (*pbRet) { hr = S_OK; } else { 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; UNICODE_STRING SecretName; UNICODE_STRING SecretValue;
InitializeObjectAttributes(&ObjectAttributes, NULL, 0L, (HANDLE)NULL, NULL);
Status = LsaOpenPolicy(NULL, &ObjectAttributes, POLICY_CREATE_SECRET, &LsaHandle); if (!NT_SUCCESS(Status)) return Status;
RtlInitUnicodeString(&SecretName, L"DefaultPassword"); RtlInitUnicodeString(&SecretValue, szPassword);
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)) { // 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();
lstrcpyn(_szLoginName, pszLoginName, ARRAYSIZE(_szLoginName)); lstrcpyn(_szDomain, pszDomain, ARRAYSIZE(_szDomain));
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.
lstrcpyn(_szLoginName, var.bstrVal, ARRAYSIZE(_szLoginName)); hr = S_OK; } 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.
_InitPicture(); }
if (TEXT('\0') != _szPicture[0]) { TCHAR szNewPicture[ARRAYSIZE(_szPicture)]; LPTSTR szFileName; LPTSTR szFileExt;
szFileName = PathFindFileName(&_szPicture[7]); szFileExt = PathFindExtension(szFileName);
lstrcpyn(szNewPicture, _szPicture, (int)((szFileName - _szPicture) + 1)); lstrcatn(szNewPicture, usri0.usri0_name, ARRAYSIZE(szNewPicture)); lstrcatn(szNewPicture, szFileExt, ARRAYSIZE(szNewPicture));
if ( MoveFileEx(&_szPicture[7], &szNewPicture[7], MOVEFILE_REPLACE_EXISTING) ) { lstrcpyn(_szPicture, szNewPicture, ARRAYSIZE(_szPicture)); } else { // Give up and just try to delete the old picture
// (otherwise it will be abandoned).
DeleteFile(&_szPicture[7]); _szPicture[0] = _TEOF; } }
// LoginName was successfully changed. Remember to update our
// local copy
lstrcpyn(_szLoginName, usri0.usri0_name, ARRAYSIZE(_szLoginName)); hr = S_OK; } 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) ) { PathCreateFromUrl(var.bstrVal, szNewPicturePath, &dwSize, NULL); } else { lstrcpyn(szNewPicturePath, var.bstrVal, ARRAYSIZE(szNewPicturePath)); }
// REVIEW (phellar) : we build the URL string ourself so we know it's of the form,
// file://<path>, the path starts on the 7th character.
if ( _TEOF == _szPicture[0] || StrCmpI(szNewPicturePath, &_szPicture[7]) != 0 ) { hr = _SetPicture(szNewPicturePath); } else { // Src and Dest paths are the same
// nothing to do
hr = S_OK; } } 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;
PathCombine(szHintKey, c_szRegRoot, _szLoginName); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, c_szPictureSrcVal, &dwType, NULL, &dwSize) && 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;
PathCombine(szHintKey, c_szRegRoot, _szLoginName); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, szHintKey, NULL, &dwType, NULL, &dwSize) && 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].szActualGroupName) == 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
wnsprintf(szDomainAndName, ARRAYSIZE(szDomainAndName), TEXT("%s\\%s"), _szDomain, _szLoginName);
lgrmi3.lgrmi3_domainandname = szDomainAndName;
nasRet = NetLocalGroupAddMembers( NULL, g_groupname_map[var.lVal].szActualGroupName, 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].szActualGroupName, 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, reinterpret_cast<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, reinterpret_cast<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<HKEY>(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<HKEY>(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;
lstrcpyn(_szPicture, TEXT("file://"), ARRAYSIZE(_szPicture)); hr = SHGetUserPicturePath(_szLoginName, SHGUPP_FLAG_CREATE, &_szPicture[7]);
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); }
lstrcpyn(_szPicture, TEXT("file://"), ARRAYSIZE(_szPicture)); hr = SHGetUserPicturePath(_szLoginName, 0, &_szPicture[7]);
if ( FAILED(hr) ) { lstrcatn(_szPicture, pszNewPicturePath, ARRAYSIZE(_szPicture)); hr = S_OK; } }
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.
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];
PathCombine(szKey, TEXT("MACHINE"), szHintKey); wnsprintf(szSD, ARRAYSIZE(szSD), TEXT("D:(A;;0x2;;;%s)"), // 0x2 = KEY_SET_VALUE
_pszSID); SetDacl(szKey, SE_REGISTRY_KEY, szSD); } } RegCloseKey(hkHints); } } 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; }
|