You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1289 lines
39 KiB
1289 lines
39 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1993 - 1999.
|
|
//
|
|
// File: EnumUsers.cpp
|
|
//
|
|
// Contents: implementation of CLogonEnumUsers
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "priv.h"
|
|
|
|
#include "resource.h"
|
|
#include "UserOM.h"
|
|
#include <lmaccess.h> // for NetQueryDisplayInformation
|
|
#include <lmapibuf.h> // for NetApiBufferFree
|
|
#include <lmerr.h> // for NERR_Success
|
|
|
|
#include <sddl.h> // for ConvertSidToStringSid
|
|
#include <userenv.h> // for DeleteProfile
|
|
#include <aclapi.h> // for TreeResetNamedSecurityInfo
|
|
#include <tokenutil.h> // for CPrivilegeEnable
|
|
|
|
#include <GinaIPC.h>
|
|
#include <MSGinaExports.h>
|
|
|
|
|
|
HRESULT BackupUserData(LPCTSTR pszSid, LPTSTR pszProfilePath, LPCTSTR pszDestPath);
|
|
DWORD EnsureAdminFileAccess(LPTSTR pszPath);
|
|
|
|
|
|
//
|
|
// IUnknown Interface
|
|
//
|
|
|
|
ULONG CLogonEnumUsers::AddRef()
|
|
{
|
|
_cRef++;
|
|
return _cRef;
|
|
}
|
|
|
|
|
|
ULONG CLogonEnumUsers::Release()
|
|
{
|
|
ASSERT(_cRef > 0);
|
|
_cRef--;
|
|
|
|
if (_cRef > 0)
|
|
{
|
|
return _cRef;
|
|
}
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
|
|
HRESULT CLogonEnumUsers::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CLogonEnumUsers, IDispatch),
|
|
QITABENT(CLogonEnumUsers, IEnumVARIANT),
|
|
QITABENT(CLogonEnumUsers, ILogonEnumUsers),
|
|
{0},
|
|
};
|
|
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
|
|
//
|
|
// IDispatch Interface
|
|
//
|
|
|
|
STDMETHODIMP CLogonEnumUsers::GetTypeInfoCount(UINT* pctinfo)
|
|
{
|
|
return CIDispatchHelper::GetTypeInfoCount(pctinfo);
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
|
|
{
|
|
return CIDispatchHelper::GetTypeInfo(itinfo, lcid, pptinfo);
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
|
|
{
|
|
return CIDispatchHelper::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::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);
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::Next(ULONG cUsers, VARIANT* rgvar, ULONG* pcUsersFetched)
|
|
{
|
|
UNREFERENCED_PARAMETER(cUsers);
|
|
UNREFERENCED_PARAMETER(rgvar);
|
|
|
|
*pcUsersFetched = 0;
|
|
return E_NOTIMPL;
|
|
}
|
|
STDMETHODIMP CLogonEnumUsers::Skip(ULONG cUsers)
|
|
{
|
|
UNREFERENCED_PARAMETER(cUsers);
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
STDMETHODIMP CLogonEnumUsers::Reset()
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
STDMETHODIMP CLogonEnumUsers::Clone(IEnumVARIANT** ppenum)
|
|
{
|
|
*ppenum = 0;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//
|
|
// ILogonEnumUsers Interface
|
|
//
|
|
|
|
STDMETHODIMP CLogonEnumUsers::get_Domain(BSTR* pbstr)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (pbstr)
|
|
{
|
|
*pbstr = SysAllocString(_szDomain);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::put_Domain(BSTR bstr)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (bstr)
|
|
{
|
|
hr = StringCchCopy(_szDomain, ARRAYSIZE(_szDomain), bstr);
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::get_EnumFlags(ILUEORDER* porder)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (porder)
|
|
{
|
|
*porder = _enumorder;
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::put_EnumFlags(ILUEORDER order)
|
|
{
|
|
_enumorder = order;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CLogonEnumUsers::get_currentUser(ILogonUser** ppLogonUserInfo)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
*ppLogonUserInfo = NULL;
|
|
if (ppLogonUserInfo)
|
|
{
|
|
WCHAR wszUsername[UNLEN+1];
|
|
DWORD cch = UNLEN;
|
|
|
|
if (GetUserNameW(wszUsername, &cch))
|
|
{
|
|
hr = _GetUserByName(wszUsername, ppLogonUserInfo);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::get_length(UINT* pcUsers)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!_hdpaUsers)
|
|
{
|
|
// need to go enumerate all of the users
|
|
hr = _EnumerateUsers();
|
|
if (FAILED(hr))
|
|
{
|
|
TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (pcUsers)
|
|
{
|
|
*pcUsers = (UINT)DPA_GetPtrCount(_hdpaUsers);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::item(VARIANT varUserID, ILogonUser** ppLogonUserInfo)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
|
|
*ppLogonUserInfo = NULL;
|
|
|
|
if (varUserID.vt == (VT_BYREF | VT_VARIANT) && varUserID.pvarVal)
|
|
{
|
|
// This is sortof gross, but if we are passed a pointer to another variant, simply
|
|
// update our copy here...
|
|
varUserID = *(varUserID.pvarVal);
|
|
}
|
|
|
|
switch (varUserID.vt)
|
|
{
|
|
case VT_ERROR:
|
|
// BUGBUG (reinerf) - what do we do here??
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
|
|
case VT_I2:
|
|
varUserID.lVal = (long)varUserID.iVal;
|
|
// fall through...
|
|
case VT_I4:
|
|
hr = _GetUserByIndex(varUserID.lVal, ppLogonUserInfo);
|
|
break;
|
|
case VT_BSTR:
|
|
hr = _GetUserByName(varUserID.bstrVal, ppLogonUserInfo);
|
|
break;
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::_NewEnum(IUnknown** ppunk)
|
|
{
|
|
return QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::create(BSTR bstrLoginName, ILogonUser **ppLogonUser)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (bstrLoginName && *bstrLoginName)
|
|
{
|
|
NET_API_STATUS nasRet;
|
|
USER_INFO_1 usri1 = {0};
|
|
|
|
usri1.usri1_name = bstrLoginName;
|
|
usri1.usri1_priv = USER_PRIV_USER;
|
|
usri1.usri1_flags = UF_NORMAL_ACCOUNT | UF_SCRIPT | UF_DONT_EXPIRE_PASSWD;
|
|
|
|
nasRet = NetUserAdd(NULL, // local computer
|
|
1, // structure level
|
|
(LPBYTE)&usri1, // user infomarmation
|
|
NULL); // don't care
|
|
|
|
if (nasRet == NERR_PasswordTooShort)
|
|
{
|
|
// Password policy is in effect. Set UF_PASSWD_NOTREQD so we can
|
|
// create the account with no password, and remove
|
|
// UF_DONT_EXPIRE_PASSWD.
|
|
//
|
|
// We will then expire the password below, to force the user to
|
|
// change it at first logon.
|
|
|
|
usri1.usri1_flags = (usri1.usri1_flags & ~UF_DONT_EXPIRE_PASSWD) | UF_PASSWD_NOTREQD;
|
|
nasRet = NetUserAdd(NULL, // local computer
|
|
1, // structure level
|
|
(LPBYTE)&usri1, // user infomarmation
|
|
NULL); // don't care
|
|
}
|
|
|
|
if (nasRet == NERR_Success)
|
|
{
|
|
TCHAR szDomainAndName[256];
|
|
LOCALGROUP_MEMBERS_INFO_3 lgrmi3;
|
|
|
|
hr = StringCchPrintf(szDomainAndName,
|
|
ARRAYSIZE(szDomainAndName),
|
|
TEXT("%s\\%s"),
|
|
_szDomain,
|
|
bstrLoginName);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lgrmi3.lgrmi3_domainandname = szDomainAndName;
|
|
|
|
// by default newly created accounts will be child accounts
|
|
|
|
nasRet = NetLocalGroupAddMembers(
|
|
NULL,
|
|
TEXT("Users"),
|
|
3,
|
|
(LPBYTE)&lgrmi3,
|
|
1);
|
|
|
|
if (usri1.usri1_flags & UF_PASSWD_NOTREQD)
|
|
{
|
|
// Expire the password to force the user to change it at
|
|
// first logon.
|
|
|
|
PUSER_INFO_4 pusri4;
|
|
nasRet = NetUserGetInfo(NULL, bstrLoginName, 4, (LPBYTE*)&pusri4);
|
|
if (nasRet == NERR_Success)
|
|
{
|
|
pusri4->usri4_password_expired = TRUE;
|
|
nasRet = NetUserSetInfo(NULL, bstrLoginName, 4, (LPBYTE)pusri4, NULL);
|
|
NetApiBufferFree(pusri4);
|
|
}
|
|
}
|
|
|
|
if ( SUCCEEDED(CLogonUser::Create(bstrLoginName, TEXT(""), _szDomain, IID_ILogonUser, (LPVOID*)ppLogonUser)) )
|
|
{
|
|
if ( _hdpaUsers && DPA_AppendPtr(_hdpaUsers, *ppLogonUser) != -1 )
|
|
{
|
|
(*ppLogonUser)->AddRef();
|
|
}
|
|
else
|
|
{
|
|
// Invalidate the cached user infomation forcing
|
|
// a reenumeration the next time a client uses
|
|
// this object
|
|
_DestroyHDPAUsers();
|
|
}
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(nasRet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT _BuildBackupPath(ILogonUser *pLogonUser, LPCWSTR pszLoginName, LPCWSTR pszDir, LPWSTR szPath)
|
|
{
|
|
HRESULT hr;
|
|
WCHAR szName[MAX_PATH];
|
|
VARIANT varDisplayName = {0};
|
|
|
|
szName[0] = L'\0';
|
|
|
|
pLogonUser->get_setting(L"DisplayName", &varDisplayName);
|
|
|
|
if ((varDisplayName.vt == VT_BSTR) && varDisplayName.bstrVal && *varDisplayName.bstrVal)
|
|
{
|
|
hr = StringCchCopyW(szName, ARRAYSIZE(szName), varDisplayName.bstrVal);
|
|
}
|
|
else
|
|
{
|
|
hr = StringCchCopyW(szName, ARRAYSIZE(szName), pszLoginName);
|
|
}
|
|
|
|
if (FAILED(hr) ||
|
|
(PathCleanupSpec(pszDir, szName) & (PCS_PATHTOOLONG | PCS_FATAL)) ||
|
|
(szName[0] == L'\0'))
|
|
{
|
|
if (LoadStringW(HINST_THISDLL, IDS_DEFAULT_BACKUP_PATH, szName, ARRAYSIZE(szName)))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (!PathCombineW(szPath, pszDir, szName))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLogonEnumUsers::remove(VARIANT varUserId, VARIANT varBackupPath, VARIANT_BOOL *pbSuccess)
|
|
{
|
|
HRESULT hr;
|
|
ILogonUser *pLogonUser;
|
|
|
|
// TODO: Check for multi-session. If the user is logged on,
|
|
// forcibly log them off.
|
|
|
|
*pbSuccess = VARIANT_FALSE;
|
|
hr = S_FALSE;
|
|
pLogonUser = NULL;
|
|
|
|
if (IsUserAnAdmin() &&
|
|
SUCCEEDED(item(varUserId, &pLogonUser)))
|
|
{
|
|
HRESULT hrSid;
|
|
NET_API_STATUS nasRet;
|
|
VARIANT varLoginName = {0};
|
|
VARIANT varStringSid = {0};
|
|
|
|
pLogonUser->get_setting(L"LoginName", &varLoginName);
|
|
hrSid = pLogonUser->get_setting(L"SID", &varStringSid);
|
|
|
|
ASSERT(varLoginName.vt == VT_BSTR);
|
|
|
|
if (SUCCEEDED(hrSid))
|
|
{
|
|
TCHAR szKey[MAX_PATH];
|
|
TCHAR szProfilePath[MAX_PATH];
|
|
|
|
szProfilePath[0] = TEXT('\0');
|
|
|
|
// First, get the profile path
|
|
DWORD dwSize = sizeof(szProfilePath);
|
|
|
|
if (SUCCEEDED(StringCchCopy(szKey,
|
|
ARRAYSIZE(szKey),
|
|
TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"))) &&
|
|
SUCCEEDED(StringCchCat(szKey,
|
|
ARRAYSIZE(szKey),
|
|
varStringSid.bstrVal)) &&
|
|
(ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE,
|
|
szKey,
|
|
TEXT("ProfileImagePath"),
|
|
NULL,
|
|
szProfilePath,
|
|
&dwSize)))
|
|
{
|
|
// Reset ACLs on the profile so we can backup files and
|
|
// later delete the profile.
|
|
EnsureAdminFileAccess(szProfilePath);
|
|
|
|
// Backup the user's files, if requested
|
|
if (varBackupPath.vt == VT_BSTR && varBackupPath.bstrVal && *varBackupPath.bstrVal)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
|
|
hr = _BuildBackupPath(pLogonUser, varLoginName.bstrVal, varBackupPath.bstrVal, szPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(varStringSid.vt == VT_BSTR);
|
|
hr = BackupUserData(varStringSid.bstrVal, szProfilePath, szPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
nasRet = NetUserDel(NULL, varLoginName.bstrVal);
|
|
|
|
// NERR_UserNotFound can happen if the account was deleted via
|
|
// some other mechanism (e.g. lusrmgr.msc). However, we know
|
|
// that the account existed recently, so try to clean up the
|
|
// picture, profile, etc. and remove the user from our DPA.
|
|
|
|
if ((nasRet == NERR_Success) || (nasRet == NERR_UserNotFound))
|
|
{
|
|
TCHAR szHintKey[MAX_PATH];
|
|
int iUserIndex;
|
|
|
|
// Delete the user's picture if it exists
|
|
SHSetUserPicturePath(varLoginName.bstrVal, 0, NULL);
|
|
|
|
// Delete the user's profile
|
|
if (SUCCEEDED(hrSid))
|
|
{
|
|
ASSERT(varStringSid.vt == VT_BSTR);
|
|
DeleteProfile(varStringSid.bstrVal, NULL, NULL);
|
|
}
|
|
|
|
// Delete the user's hint
|
|
if (PathCombine(szHintKey, c_szRegRoot, varLoginName.bstrVal))
|
|
{
|
|
SHDeleteKey(HKEY_LOCAL_MACHINE, szHintKey);
|
|
}
|
|
|
|
// Indicate success
|
|
*pbSuccess = VARIANT_TRUE;
|
|
hr = S_OK;
|
|
|
|
// Patch up the list of users
|
|
iUserIndex = DPA_GetPtrIndex(_hdpaUsers, pLogonUser);
|
|
if ( iUserIndex != -1 )
|
|
{
|
|
// Release ref held by DPA and remove from DPA
|
|
pLogonUser->Release();
|
|
DPA_DeletePtr(_hdpaUsers, iUserIndex);
|
|
}
|
|
else
|
|
{
|
|
// Invalidate the cached user infomation forcing
|
|
// a reenumeration the next time a client uses
|
|
// this object
|
|
_DestroyHDPAUsers();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(nasRet);
|
|
}
|
|
}
|
|
|
|
pLogonUser->Release();
|
|
|
|
SysFreeString(varLoginName.bstrVal);
|
|
SysFreeString(varStringSid.bstrVal);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CLogonEnumUsers::_GetUserByName(BSTR bstrLoginName, ILogonUser** ppLogonUserInfo)
|
|
{
|
|
HRESULT hr;
|
|
INT cUsers, cRet;
|
|
ILogonUser *pLogonUser;
|
|
VARIANT varLoginName;
|
|
int i;
|
|
|
|
if (!_hdpaUsers)
|
|
{
|
|
// need to go enumerate all of the users.
|
|
hr = _EnumerateUsers();
|
|
if (FAILED(hr))
|
|
{
|
|
TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
cUsers = DPA_GetPtrCount(_hdpaUsers);
|
|
hr = E_INVALIDARG;
|
|
for (i = 0; i < cUsers; i++)
|
|
{
|
|
pLogonUser = (ILogonUser*)DPA_FastGetPtr(_hdpaUsers, i);
|
|
pLogonUser->get_setting(L"LoginName", &varLoginName);
|
|
|
|
ASSERT(varLoginName.vt == VT_BSTR);
|
|
cRet = StrCmpW(bstrLoginName, varLoginName.bstrVal);
|
|
SysFreeString(varLoginName.bstrVal);
|
|
|
|
if ( cRet == 0 )
|
|
{
|
|
*ppLogonUserInfo = pLogonUser;
|
|
(*ppLogonUserInfo)->AddRef();
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CLogonEnumUsers::_GetUserByIndex(LONG lUserID, ILogonUser** ppLogonUserInfo)
|
|
{
|
|
HRESULT hr;
|
|
int cUsers;
|
|
|
|
*ppLogonUserInfo = NULL;
|
|
|
|
if (!_hdpaUsers)
|
|
{
|
|
// need to go enumerate all of the users.
|
|
hr = _EnumerateUsers();
|
|
if (FAILED(hr))
|
|
{
|
|
TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
cUsers = DPA_GetPtrCount(_hdpaUsers);
|
|
|
|
if ((cUsers > 0) && (lUserID >= 0) && (lUserID < cUsers))
|
|
{
|
|
*ppLogonUserInfo = (ILogonUser*)DPA_FastGetPtr(_hdpaUsers, lUserID);
|
|
(*ppLogonUserInfo)->AddRef();
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDAPI_(int) ReleaseLogonUserCallback(LPVOID pData1, LPVOID pData2)
|
|
{
|
|
UNREFERENCED_PARAMETER(pData2);
|
|
|
|
ILogonUser* pUser = (ILogonUser*)pData1;
|
|
pUser->Release();
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
void CLogonEnumUsers::_DestroyHDPAUsers()
|
|
{
|
|
HDPA hdpaToFree = (HDPA)InterlockedExchangePointer(reinterpret_cast<void**>(&_hdpaUsers), NULL);
|
|
|
|
if (hdpaToFree)
|
|
{
|
|
DPA_DestroyCallback(hdpaToFree, ReleaseLogonUserCallback, 0);
|
|
}
|
|
}
|
|
|
|
// creates the _hdpaUsers for each user on the system
|
|
HRESULT CLogonEnumUsers::_EnumerateUsers()
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
NET_API_STATUS nasRet;
|
|
GINA_USER_INFORMATION* pgui = NULL;
|
|
DWORD dwEntriesRead = 0;
|
|
|
|
nasRet = ShellGetUserList(FALSE, // don't remove Guest
|
|
&dwEntriesRead,
|
|
(LPVOID*)&pgui);
|
|
if ((nasRet == NERR_Success) || (nasRet == ERROR_MORE_DATA))
|
|
{
|
|
if (_hdpaUsers)
|
|
{
|
|
// we have an old data in the dpa and we should dump it and start over
|
|
_DestroyHDPAUsers();
|
|
}
|
|
|
|
// create a dpa with spaces for all of the users
|
|
_hdpaUsers = DPA_Create(dwEntriesRead);
|
|
|
|
if (_hdpaUsers)
|
|
{
|
|
if (dwEntriesRead != 0)
|
|
{
|
|
GINA_USER_INFORMATION* pguiCurrent;
|
|
UINT uEntry;
|
|
|
|
// cycle through and add each user to the hdpa
|
|
for (uEntry = 0, pguiCurrent = pgui; uEntry < dwEntriesRead; uEntry++, pguiCurrent++)
|
|
{
|
|
CLogonUser* pUser;
|
|
|
|
if (pguiCurrent->dwFlags & UF_ACCOUNTDISABLE)
|
|
{
|
|
// skip users whos account is disabled
|
|
continue;
|
|
}
|
|
|
|
if (SUCCEEDED(CLogonUser::Create(pguiCurrent->pszName, pguiCurrent->pszFullName, pguiCurrent->pszDomain, IID_ILogonUser, (void**)&pUser)))
|
|
{
|
|
ASSERT(pUser);
|
|
|
|
if (DPA_AppendPtr(_hdpaUsers, pUser) != -1)
|
|
{
|
|
// success! we added this user to the hdpa
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_WARNING, "CLogonEnumUsers::_EnumerateUsers: failed to add new user to the DPA!");
|
|
pUser->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (pgui != NULL)
|
|
{
|
|
LocalFree(pgui);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_WARNING, "CLogonEnumUsers::_EnumerateUsers: NetQueryDisplayInformation failed!!");
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
CLogonEnumUsers::CLogonEnumUsers() : _cRef(1), CIDispatchHelper(&IID_ILogonEnumUsers, &LIBID_SHGINALib)
|
|
{
|
|
DllAddRef();
|
|
}
|
|
|
|
|
|
CLogonEnumUsers::~CLogonEnumUsers()
|
|
{
|
|
ASSERT(_cRef == 0);
|
|
_DestroyHDPAUsers();
|
|
DllRelease();
|
|
}
|
|
|
|
|
|
STDAPI CLogonEnumUsers_Create(REFIID riid, LPVOID* ppv)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CLogonEnumUsers* pEnumUsers = new CLogonEnumUsers;
|
|
|
|
if (pEnumUsers)
|
|
{
|
|
hr = pEnumUsers->QueryInterface(riid, ppv);
|
|
pEnumUsers->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
DWORD LoadHive(HKEY hKey, LPCTSTR pszSubKey, LPCTSTR pszHive)
|
|
{
|
|
DWORD dwErr;
|
|
BOOLEAN bWasEnabled;
|
|
NTSTATUS status;
|
|
|
|
status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &bWasEnabled);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
dwErr = RegLoadKey(hKey, pszSubKey, pszHive);
|
|
|
|
RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled);
|
|
}
|
|
else
|
|
{
|
|
dwErr = RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
DWORD UnloadHive(HKEY hKey, LPCTSTR pszSubKey)
|
|
{
|
|
DWORD dwErr;
|
|
BOOLEAN bWasEnabled;
|
|
NTSTATUS status;
|
|
|
|
status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &bWasEnabled);
|
|
|
|
if ( NT_SUCCESS(status) )
|
|
{
|
|
dwErr = RegUnLoadKey(hKey, pszSubKey);
|
|
|
|
RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled);
|
|
}
|
|
else
|
|
{
|
|
dwErr = RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
void DeleteFilesInTree(LPCTSTR pszDir, LPCTSTR pszFilter)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA fd;
|
|
|
|
// This is best effort only. All errors are ignored
|
|
// and no error or success code is returned.
|
|
|
|
// Look for files matching the filter and delete them
|
|
if (PathCombine(szPath, pszDir, pszFilter))
|
|
{
|
|
hFind = FindFirstFileEx(szPath, FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, 0);
|
|
if (hFind != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
if (PathCombine(szPath, pszDir, fd.cFileName))
|
|
{
|
|
DeleteFile(szPath);
|
|
}
|
|
}
|
|
}
|
|
while (FindNextFile(hFind, &fd));
|
|
|
|
FindClose(hFind);
|
|
}
|
|
}
|
|
|
|
// Look for subdirectories and recurse into them
|
|
if (PathCombine(szPath, pszDir, TEXT("*")))
|
|
{
|
|
hFind = FindFirstFileEx(szPath,
|
|
FindExInfoStandard,
|
|
&fd,
|
|
FindExSearchLimitToDirectories,
|
|
NULL,
|
|
0);
|
|
if (hFind != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if (PathIsDotOrDotDot(fd.cFileName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// FindExSearchLimitToDirectories is only an advisory flag,
|
|
// so need to check for FILE_ATTRIBUTE_DIRECTORY here.
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
|
|
{
|
|
if (PathCombine(szPath, pszDir, fd.cFileName))
|
|
{
|
|
DeleteFilesInTree(szPath, pszFilter);
|
|
}
|
|
|
|
// Expect this to fail if the dir is non-empty
|
|
RemoveDirectory(szPath);
|
|
}
|
|
}
|
|
while (FindNextFile(hFind, &fd));
|
|
|
|
FindClose(hFind);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
_PathIsEqualOrSubFolder(
|
|
LPTSTR pszParent,
|
|
LPCTSTR pszSubFolder
|
|
)
|
|
{
|
|
TCHAR szCommon[MAX_PATH];
|
|
|
|
// PathCommonPrefix() always removes the slash on common
|
|
return (pszParent[0] && PathRemoveBackslash(pszParent)
|
|
&& PathCommonPrefix(pszParent, pszSubFolder, szCommon)
|
|
&& lstrcmpi(pszParent, szCommon) == 0);
|
|
}
|
|
|
|
HRESULT BackupUserData(LPCTSTR pszSid, LPTSTR pszProfilePath, LPCTSTR pszDestPath)
|
|
{
|
|
DWORD dwErr;
|
|
TCHAR szHive[MAX_PATH];
|
|
|
|
// We will copy these special folders
|
|
const LPCTSTR aValueNames[] =
|
|
{
|
|
TEXT("Desktop"),
|
|
TEXT("Personal"),
|
|
TEXT("My Pictures") // must come after Personal
|
|
};
|
|
|
|
if ( pszSid == NULL || *pszSid == TEXT('\0') ||
|
|
pszProfilePath == NULL || *pszProfilePath == TEXT('\0') ||
|
|
pszDestPath == NULL || *pszDestPath == TEXT('\0') )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Before we do anything else, make sure the destination directory
|
|
// exists. Create this even if we don't copy any files below, so the
|
|
// user sees that something happened.
|
|
dwErr = SHCreateDirectoryEx(NULL, pszDestPath, NULL);
|
|
|
|
if ( dwErr == ERROR_FILE_EXISTS || dwErr == ERROR_ALREADY_EXISTS )
|
|
dwErr = ERROR_SUCCESS;
|
|
|
|
if ( dwErr != ERROR_SUCCESS )
|
|
return dwErr;
|
|
|
|
// Load the user's hive
|
|
if (PathCombine(szHive, pszProfilePath, TEXT("ntuser.dat")))
|
|
{
|
|
dwErr = LoadHive(HKEY_USERS, pszSid, szHive);
|
|
}
|
|
else
|
|
{
|
|
dwErr = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
if ( dwErr == ERROR_SUCCESS )
|
|
{
|
|
HKEY hkShellFolders = NULL;
|
|
TCHAR szKey[MAX_PATH];
|
|
|
|
// Open the Shell Folders key for the user. We use "Shell Folders"
|
|
// here rather than "User Shell Folders" so we don't have to expand
|
|
// ENV strings for the user (we don't have a token).
|
|
//
|
|
// The only way Shell Folders can be out of date is if someone
|
|
// changed User Shell Folders since the last time the target user
|
|
// logged on, and didn't subsequently call SHGetFolderPath. This
|
|
// is a very small risk, but it's possible.
|
|
//
|
|
// If we encounter problems here, then we will need to build a
|
|
// pseudo-environment block for the user containing USERNAME and
|
|
// USERPROFILE (at least) so we can switch to User Shell Folders
|
|
// and do the ENV substitution.
|
|
|
|
if (SUCCEEDED(StringCchCopy(szKey, ARRAYSIZE(szKey), pszSid)) &&
|
|
SUCCEEDED(StringCchCat(szKey,
|
|
ARRAYSIZE(szKey),
|
|
TEXT("\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"))))
|
|
{
|
|
dwErr = RegOpenKeyEx(HKEY_USERS,
|
|
szKey,
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&hkShellFolders);
|
|
}
|
|
else
|
|
{
|
|
dwErr == ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
if ( dwErr == ERROR_SUCCESS )
|
|
{
|
|
LPTSTR pszFrom;
|
|
LPTSTR pszTo;
|
|
|
|
// Allocate 2 buffers for double-NULL terminated lists of paths.
|
|
// Note that the buffers have 1 extra char (compared to cchFrom
|
|
// and cchTo below) and are zero-inited. This extra char ensures
|
|
// that the list is double-NULL terminated.
|
|
|
|
pszFrom = (LPTSTR)LocalAlloc(LPTR, (MAX_PATH+ 1) * ARRAYSIZE(aValueNames) * sizeof(TCHAR));
|
|
pszTo = (LPTSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * ARRAYSIZE(aValueNames) * sizeof(TCHAR));
|
|
|
|
if (pszFrom && pszTo)
|
|
{
|
|
int i;
|
|
|
|
// Get each source directory from the registry, build
|
|
// a corresponding destination path, and add the paths
|
|
// to the lists for SHFileOperation.
|
|
|
|
for (i = 0; i < ARRAYSIZE(aValueNames); i++)
|
|
{
|
|
// Copy the source path directly into the list
|
|
DWORD dwSize = MAX_PATH * sizeof(TCHAR); // we allocated enough room for each string to be MAX_PATH above
|
|
dwErr = RegQueryValueEx(hkShellFolders,
|
|
aValueNames[i],
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)pszFrom,
|
|
&dwSize);
|
|
|
|
if (dwErr == ERROR_SUCCESS)
|
|
{
|
|
if (!_PathIsEqualOrSubFolder(pszProfilePath, pszFrom))
|
|
{
|
|
// We only move folders underneath the profile path. The reason for this is
|
|
// two fold - first only the profile path will be deleted when the users profile
|
|
// is deleted, and second a malicious user could point their mydocs at c:\ and
|
|
// this code would be run as an administrator and we would go and try to move/delete
|
|
// the contents of the whole drive
|
|
continue;
|
|
}
|
|
|
|
// Build a destination path with the same
|
|
// leaf name as the source.
|
|
PathRemoveBackslash(pszFrom);
|
|
|
|
LPCTSTR pszDir = PathFindFileName(pszFrom);
|
|
if (PathIsFileSpec(pszDir) &&
|
|
PathCombine(pszTo, pszDestPath, pszDir))
|
|
{
|
|
// we sucessfully have the pszFrom->pszTo mapping for this regkey
|
|
pszFrom += lstrlen(pszFrom) + 1;
|
|
pszTo += lstrlen(pszTo) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Did we find anything?
|
|
if ((*pszFrom != TEXT('\0') && (*pszTo != TEXT('\0'))))
|
|
{
|
|
SHFILEOPSTRUCT fo = {0};
|
|
|
|
fo.hwnd = NULL;
|
|
fo.wFunc = FO_MOVE;
|
|
fo.pFrom = pszFrom; // should already be double-null terminated (was alloced w/ zero init)
|
|
fo.pTo = pszTo; // should already be double-null terminated (was alloced w/ zero init)
|
|
fo.fFlags = FOF_MULTIDESTFILES |
|
|
FOF_NOCONFIRMATION |
|
|
FOF_NOCONFIRMMKDIR |
|
|
FOF_NOCOPYSECURITYATTRIBS |
|
|
FOF_NOERRORUI |
|
|
FOF_RENAMEONCOLLISION;
|
|
|
|
// Move everything in one shot
|
|
dwErr = SHFileOperation(&fo);
|
|
|
|
// We get ERROR_CANCELLED when My Pictures is contained
|
|
// within My Documents, which is the normal case. In this
|
|
// case My Pictures is moved along with My Documents and
|
|
// doesn't exist any more in the source location when the
|
|
// copy engine gets around to moving My Pictures.
|
|
//
|
|
// We have to continue to specify My Pictures separately
|
|
// to account for any cases where it is not contained
|
|
// in My Documents, even though that's relatively rare.
|
|
//
|
|
// Note that putting My Pictures ahead of Personal in
|
|
// aValueNames above would avoid the error, but My Pictures
|
|
// would no longer be under My Documents after the move.
|
|
if (dwErr == ERROR_CANCELLED)
|
|
{
|
|
dwErr = ERROR_SUCCESS;
|
|
}
|
|
|
|
if (dwErr == ERROR_SUCCESS)
|
|
{
|
|
// Now go back and delete stuff we didn't really
|
|
// want (i.e. shortcut files)
|
|
DeleteFilesInTree(pszDestPath, TEXT("*.lnk"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErr = ERROR_OUTOFMEMORY;
|
|
}
|
|
|
|
if (pszFrom != NULL)
|
|
{
|
|
LocalFree(pszFrom);
|
|
}
|
|
|
|
if (pszTo != NULL)
|
|
{
|
|
LocalFree(pszTo);
|
|
}
|
|
|
|
// Close the Shell Folders key
|
|
RegCloseKey(hkShellFolders);
|
|
}
|
|
|
|
// Unload the hive
|
|
UnloadHive(HKEY_USERS, pszSid);
|
|
}
|
|
|
|
if ( dwErr == ERROR_FILE_NOT_FOUND )
|
|
{
|
|
// Something was missing, possibly the entire profile (e.g. if the
|
|
// user had never logged on), or possibly just one of the Shell Folders
|
|
// reg values. It just means there was less work to do.
|
|
dwErr = ERROR_SUCCESS;
|
|
}
|
|
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
|
|
|
|
BOOL _SetFileSecurityUsingNTName(LPWSTR pObjectName,
|
|
PSECURITY_DESCRIPTOR pSD,
|
|
PBOOL pbIsFile)
|
|
{
|
|
NTSTATUS Status;
|
|
UNICODE_STRING usFileName;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
HANDLE hFile = NULL;
|
|
|
|
Status = RtlInitUnicodeStringEx(&usFileName, pObjectName);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
InitializeObjectAttributes(&Obja,
|
|
&usFileName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = NtOpenFile(&hFile,
|
|
WRITE_DAC,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN_REPARSE_POINT);
|
|
|
|
if (Status == STATUS_INVALID_PARAMETER)
|
|
{
|
|
Status = NtOpenFile(&hFile,
|
|
WRITE_DAC,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
0);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (!SetKernelObjectSecurity(hFile,
|
|
DACL_SECURITY_INFORMATION,
|
|
pSD))
|
|
{
|
|
Status = RtlGetLastNtStatus();
|
|
|
|
// We successfully opened for WRITE_DAC access, so this shouldn't fail
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
NtClose(hFile);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// That worked. Now open the file again and read attributes, to see
|
|
// if it's a file or directory. Default to File if this fails.
|
|
// See comments in _TreeResetCallback below.
|
|
//
|
|
*pbIsFile = TRUE;
|
|
|
|
if (NT_SUCCESS(NtOpenFile(&hFile,
|
|
FILE_GENERIC_READ,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
0)))
|
|
{
|
|
//
|
|
// Query the attributes for the file/dir.
|
|
//
|
|
FILE_BASIC_INFORMATION BasicFileInfo;
|
|
|
|
if (NT_SUCCESS(NtQueryInformationFile(hFile,
|
|
&IoStatusBlock,
|
|
&BasicFileInfo,
|
|
sizeof(BasicFileInfo),
|
|
FileBasicInformation)))
|
|
{
|
|
if (BasicFileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
*pbIsFile = FALSE;
|
|
}
|
|
}
|
|
|
|
NtClose(hFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
|
|
void _TreeResetCallback(LPWSTR pObjectName,
|
|
DWORD status,
|
|
PPROG_INVOKE_SETTING pInvokeSetting,
|
|
PVOID pContext,
|
|
BOOL bSecuritySet)
|
|
{
|
|
BOOL bIsFile = TRUE;
|
|
|
|
// Default is "continue"
|
|
*pInvokeSetting = ProgressInvokeEveryObject;
|
|
|
|
// Stamp the permissions on this object
|
|
_SetFileSecurityUsingNTName(pObjectName, (PSECURITY_DESCRIPTOR)pContext, &bIsFile);
|
|
|
|
//
|
|
// bSecuritySet = TRUE means TreeResetNamedSecurityInfo set the owner.
|
|
//
|
|
// status != ERROR_SUCCESS means it couldn't enumerate the child.
|
|
//
|
|
// If it's not a file, retry the operation (with no access initially,
|
|
// TreeResetNamedSecurityInfo can't get attributes and tries to
|
|
// enumerate everything as if it's a directory).
|
|
//
|
|
// Have to be careful to avoid infinite loops here. Basically, we assume
|
|
// everything is a file. If we can grant ourselves access above, we get
|
|
// good attributes and do the right thing. If not, we skip the retry.
|
|
//
|
|
if (bSecuritySet &&
|
|
(status != ERROR_SUCCESS) &&
|
|
!bIsFile)
|
|
{
|
|
*pInvokeSetting = ProgressRetryOperation;
|
|
}
|
|
}
|
|
|
|
|
|
DWORD EnsureAdminFileAccess(LPTSTR pszPath)
|
|
{
|
|
DWORD dwErr;
|
|
PSECURITY_DESCRIPTOR pSD;
|
|
|
|
const TCHAR c_szAdminSD[] = TEXT("O:BAG:BAD:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)");
|
|
|
|
if (ConvertStringSecurityDescriptorToSecurityDescriptor(c_szAdminSD, SDDL_REVISION_1, &pSD, NULL))
|
|
{
|
|
PSID pOwner = NULL;
|
|
BOOL bDefault;
|
|
|
|
CPrivilegeEnable privilege(SE_TAKE_OWNERSHIP_NAME);
|
|
|
|
GetSecurityDescriptorOwner(pSD, &pOwner, &bDefault);
|
|
|
|
//
|
|
// When the current user doesn't have any access, we have to do things
|
|
// in the correct order. For each file or directory in the tree,
|
|
// 1. Take ownership, this gives us permission to...
|
|
// 2. Set permissions, this gives us permission to...
|
|
// 3. See if it is a directory, and recurse into it
|
|
//
|
|
// TreeResetNamedSecurityInfo doesn't quite work that way, so we use
|
|
// it to set the owner and do the enumeration. The callback sets
|
|
// the permissions, and tells TreeResetNamedSecurityInfo to retry
|
|
// the enumeration if necessary.
|
|
//
|
|
dwErr = TreeResetNamedSecurityInfo(pszPath,
|
|
SE_FILE_OBJECT,
|
|
OWNER_SECURITY_INFORMATION,
|
|
pOwner,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
_TreeResetCallback,
|
|
ProgressInvokeEveryObject,
|
|
pSD);
|
|
|
|
LocalFree(pSD);
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
}
|
|
|
|
return dwErr;
|
|
}
|