Leaked source code of windows server 2003
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

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1993 - 1999.
  5. //
  6. // File: EnumUsers.cpp
  7. //
  8. // Contents: implementation of CLogonEnumUsers
  9. //
  10. //----------------------------------------------------------------------------
  11. #include "priv.h"
  12. #include "resource.h"
  13. #include "UserOM.h"
  14. #include <lmaccess.h> // for NetQueryDisplayInformation
  15. #include <lmapibuf.h> // for NetApiBufferFree
  16. #include <lmerr.h> // for NERR_Success
  17. #include <sddl.h> // for ConvertSidToStringSid
  18. #include <userenv.h> // for DeleteProfile
  19. #include <aclapi.h> // for TreeResetNamedSecurityInfo
  20. #include <tokenutil.h> // for CPrivilegeEnable
  21. #include <GinaIPC.h>
  22. #include <MSGinaExports.h>
  23. HRESULT BackupUserData(LPCTSTR pszSid, LPTSTR pszProfilePath, LPCTSTR pszDestPath);
  24. DWORD EnsureAdminFileAccess(LPTSTR pszPath);
  25. //
  26. // IUnknown Interface
  27. //
  28. ULONG CLogonEnumUsers::AddRef()
  29. {
  30. _cRef++;
  31. return _cRef;
  32. }
  33. ULONG CLogonEnumUsers::Release()
  34. {
  35. ASSERT(_cRef > 0);
  36. _cRef--;
  37. if (_cRef > 0)
  38. {
  39. return _cRef;
  40. }
  41. delete this;
  42. return 0;
  43. }
  44. HRESULT CLogonEnumUsers::QueryInterface(REFIID riid, void **ppvObj)
  45. {
  46. static const QITAB qit[] =
  47. {
  48. QITABENT(CLogonEnumUsers, IDispatch),
  49. QITABENT(CLogonEnumUsers, IEnumVARIANT),
  50. QITABENT(CLogonEnumUsers, ILogonEnumUsers),
  51. {0},
  52. };
  53. return QISearch(this, qit, riid, ppvObj);
  54. }
  55. //
  56. // IDispatch Interface
  57. //
  58. STDMETHODIMP CLogonEnumUsers::GetTypeInfoCount(UINT* pctinfo)
  59. {
  60. return CIDispatchHelper::GetTypeInfoCount(pctinfo);
  61. }
  62. STDMETHODIMP CLogonEnumUsers::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
  63. {
  64. return CIDispatchHelper::GetTypeInfo(itinfo, lcid, pptinfo);
  65. }
  66. STDMETHODIMP CLogonEnumUsers::GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
  67. {
  68. return CIDispatchHelper::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
  69. }
  70. STDMETHODIMP CLogonEnumUsers::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
  71. {
  72. return CIDispatchHelper::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
  73. }
  74. STDMETHODIMP CLogonEnumUsers::Next(ULONG cUsers, VARIANT* rgvar, ULONG* pcUsersFetched)
  75. {
  76. UNREFERENCED_PARAMETER(cUsers);
  77. UNREFERENCED_PARAMETER(rgvar);
  78. *pcUsersFetched = 0;
  79. return E_NOTIMPL;
  80. }
  81. STDMETHODIMP CLogonEnumUsers::Skip(ULONG cUsers)
  82. {
  83. UNREFERENCED_PARAMETER(cUsers);
  84. return E_NOTIMPL;
  85. }
  86. STDMETHODIMP CLogonEnumUsers::Reset()
  87. {
  88. return E_NOTIMPL;
  89. }
  90. STDMETHODIMP CLogonEnumUsers::Clone(IEnumVARIANT** ppenum)
  91. {
  92. *ppenum = 0;
  93. return E_NOTIMPL;
  94. }
  95. //
  96. // ILogonEnumUsers Interface
  97. //
  98. STDMETHODIMP CLogonEnumUsers::get_Domain(BSTR* pbstr)
  99. {
  100. HRESULT hr;
  101. if (pbstr)
  102. {
  103. *pbstr = SysAllocString(_szDomain);
  104. hr = S_OK;
  105. }
  106. else
  107. {
  108. hr = E_INVALIDARG;
  109. }
  110. return hr;
  111. }
  112. STDMETHODIMP CLogonEnumUsers::put_Domain(BSTR bstr)
  113. {
  114. HRESULT hr;
  115. if (bstr)
  116. {
  117. hr = StringCchCopy(_szDomain, ARRAYSIZE(_szDomain), bstr);
  118. }
  119. else
  120. {
  121. hr = E_INVALIDARG;
  122. }
  123. return hr;
  124. }
  125. STDMETHODIMP CLogonEnumUsers::get_EnumFlags(ILUEORDER* porder)
  126. {
  127. HRESULT hr;
  128. if (porder)
  129. {
  130. *porder = _enumorder;
  131. hr = S_OK;
  132. }
  133. else
  134. {
  135. hr = E_INVALIDARG;
  136. }
  137. return hr;
  138. }
  139. STDMETHODIMP CLogonEnumUsers::put_EnumFlags(ILUEORDER order)
  140. {
  141. _enumorder = order;
  142. return S_OK;
  143. }
  144. STDMETHODIMP CLogonEnumUsers::get_currentUser(ILogonUser** ppLogonUserInfo)
  145. {
  146. HRESULT hr = E_FAIL;
  147. *ppLogonUserInfo = NULL;
  148. if (ppLogonUserInfo)
  149. {
  150. WCHAR wszUsername[UNLEN+1];
  151. DWORD cch = UNLEN;
  152. if (GetUserNameW(wszUsername, &cch))
  153. {
  154. hr = _GetUserByName(wszUsername, ppLogonUserInfo);
  155. hr = S_OK;
  156. }
  157. }
  158. else
  159. {
  160. hr = E_INVALIDARG;
  161. }
  162. return hr;
  163. }
  164. STDMETHODIMP CLogonEnumUsers::get_length(UINT* pcUsers)
  165. {
  166. HRESULT hr;
  167. if (!_hdpaUsers)
  168. {
  169. // need to go enumerate all of the users
  170. hr = _EnumerateUsers();
  171. if (FAILED(hr))
  172. {
  173. TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
  174. return hr;
  175. }
  176. }
  177. if (pcUsers)
  178. {
  179. *pcUsers = (UINT)DPA_GetPtrCount(_hdpaUsers);
  180. hr = S_OK;
  181. }
  182. else
  183. {
  184. hr = E_INVALIDARG;
  185. }
  186. return hr;
  187. }
  188. STDMETHODIMP CLogonEnumUsers::item(VARIANT varUserID, ILogonUser** ppLogonUserInfo)
  189. {
  190. HRESULT hr = S_FALSE;
  191. *ppLogonUserInfo = NULL;
  192. if (varUserID.vt == (VT_BYREF | VT_VARIANT) && varUserID.pvarVal)
  193. {
  194. // This is sortof gross, but if we are passed a pointer to another variant, simply
  195. // update our copy here...
  196. varUserID = *(varUserID.pvarVal);
  197. }
  198. switch (varUserID.vt)
  199. {
  200. case VT_ERROR:
  201. // BUGBUG (reinerf) - what do we do here??
  202. hr = E_INVALIDARG;
  203. break;
  204. case VT_I2:
  205. varUserID.lVal = (long)varUserID.iVal;
  206. // fall through...
  207. case VT_I4:
  208. hr = _GetUserByIndex(varUserID.lVal, ppLogonUserInfo);
  209. break;
  210. case VT_BSTR:
  211. hr = _GetUserByName(varUserID.bstrVal, ppLogonUserInfo);
  212. break;
  213. default:
  214. hr = E_NOTIMPL;
  215. }
  216. return hr;
  217. }
  218. STDMETHODIMP CLogonEnumUsers::_NewEnum(IUnknown** ppunk)
  219. {
  220. return QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
  221. }
  222. STDMETHODIMP CLogonEnumUsers::create(BSTR bstrLoginName, ILogonUser **ppLogonUser)
  223. {
  224. HRESULT hr = E_FAIL;
  225. if (bstrLoginName && *bstrLoginName)
  226. {
  227. NET_API_STATUS nasRet;
  228. USER_INFO_1 usri1 = {0};
  229. usri1.usri1_name = bstrLoginName;
  230. usri1.usri1_priv = USER_PRIV_USER;
  231. usri1.usri1_flags = UF_NORMAL_ACCOUNT | UF_SCRIPT | UF_DONT_EXPIRE_PASSWD;
  232. nasRet = NetUserAdd(NULL, // local computer
  233. 1, // structure level
  234. (LPBYTE)&usri1, // user infomarmation
  235. NULL); // don't care
  236. if (nasRet == NERR_PasswordTooShort)
  237. {
  238. // Password policy is in effect. Set UF_PASSWD_NOTREQD so we can
  239. // create the account with no password, and remove
  240. // UF_DONT_EXPIRE_PASSWD.
  241. //
  242. // We will then expire the password below, to force the user to
  243. // change it at first logon.
  244. usri1.usri1_flags = (usri1.usri1_flags & ~UF_DONT_EXPIRE_PASSWD) | UF_PASSWD_NOTREQD;
  245. nasRet = NetUserAdd(NULL, // local computer
  246. 1, // structure level
  247. (LPBYTE)&usri1, // user infomarmation
  248. NULL); // don't care
  249. }
  250. if (nasRet == NERR_Success)
  251. {
  252. TCHAR szDomainAndName[256];
  253. LOCALGROUP_MEMBERS_INFO_3 lgrmi3;
  254. hr = StringCchPrintf(szDomainAndName,
  255. ARRAYSIZE(szDomainAndName),
  256. TEXT("%s\\%s"),
  257. _szDomain,
  258. bstrLoginName);
  259. if (SUCCEEDED(hr))
  260. {
  261. lgrmi3.lgrmi3_domainandname = szDomainAndName;
  262. // by default newly created accounts will be child accounts
  263. nasRet = NetLocalGroupAddMembers(
  264. NULL,
  265. TEXT("Users"),
  266. 3,
  267. (LPBYTE)&lgrmi3,
  268. 1);
  269. if (usri1.usri1_flags & UF_PASSWD_NOTREQD)
  270. {
  271. // Expire the password to force the user to change it at
  272. // first logon.
  273. PUSER_INFO_4 pusri4;
  274. nasRet = NetUserGetInfo(NULL, bstrLoginName, 4, (LPBYTE*)&pusri4);
  275. if (nasRet == NERR_Success)
  276. {
  277. pusri4->usri4_password_expired = TRUE;
  278. nasRet = NetUserSetInfo(NULL, bstrLoginName, 4, (LPBYTE)pusri4, NULL);
  279. NetApiBufferFree(pusri4);
  280. }
  281. }
  282. if ( SUCCEEDED(CLogonUser::Create(bstrLoginName, TEXT(""), _szDomain, IID_ILogonUser, (LPVOID*)ppLogonUser)) )
  283. {
  284. if ( _hdpaUsers && DPA_AppendPtr(_hdpaUsers, *ppLogonUser) != -1 )
  285. {
  286. (*ppLogonUser)->AddRef();
  287. }
  288. else
  289. {
  290. // Invalidate the cached user infomation forcing
  291. // a reenumeration the next time a client uses
  292. // this object
  293. _DestroyHDPAUsers();
  294. }
  295. hr = S_OK;
  296. }
  297. }
  298. }
  299. else
  300. {
  301. hr = HRESULT_FROM_WIN32(nasRet);
  302. }
  303. }
  304. else
  305. {
  306. hr = E_INVALIDARG;
  307. }
  308. return hr;
  309. }
  310. HRESULT _BuildBackupPath(ILogonUser *pLogonUser, LPCWSTR pszLoginName, LPCWSTR pszDir, LPWSTR szPath)
  311. {
  312. HRESULT hr;
  313. WCHAR szName[MAX_PATH];
  314. VARIANT varDisplayName = {0};
  315. szName[0] = L'\0';
  316. pLogonUser->get_setting(L"DisplayName", &varDisplayName);
  317. if ((varDisplayName.vt == VT_BSTR) && varDisplayName.bstrVal && *varDisplayName.bstrVal)
  318. {
  319. hr = StringCchCopyW(szName, ARRAYSIZE(szName), varDisplayName.bstrVal);
  320. }
  321. else
  322. {
  323. hr = StringCchCopyW(szName, ARRAYSIZE(szName), pszLoginName);
  324. }
  325. if (FAILED(hr) ||
  326. (PathCleanupSpec(pszDir, szName) & (PCS_PATHTOOLONG | PCS_FATAL)) ||
  327. (szName[0] == L'\0'))
  328. {
  329. if (LoadStringW(HINST_THISDLL, IDS_DEFAULT_BACKUP_PATH, szName, ARRAYSIZE(szName)))
  330. {
  331. hr = S_OK;
  332. }
  333. }
  334. if (SUCCEEDED(hr))
  335. {
  336. if (!PathCombineW(szPath, pszDir, szName))
  337. {
  338. hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
  339. }
  340. }
  341. return hr;
  342. }
  343. STDMETHODIMP CLogonEnumUsers::remove(VARIANT varUserId, VARIANT varBackupPath, VARIANT_BOOL *pbSuccess)
  344. {
  345. HRESULT hr;
  346. ILogonUser *pLogonUser;
  347. // TODO: Check for multi-session. If the user is logged on,
  348. // forcibly log them off.
  349. *pbSuccess = VARIANT_FALSE;
  350. hr = S_FALSE;
  351. pLogonUser = NULL;
  352. if (IsUserAnAdmin() &&
  353. SUCCEEDED(item(varUserId, &pLogonUser)))
  354. {
  355. HRESULT hrSid;
  356. NET_API_STATUS nasRet;
  357. VARIANT varLoginName = {0};
  358. VARIANT varStringSid = {0};
  359. pLogonUser->get_setting(L"LoginName", &varLoginName);
  360. hrSid = pLogonUser->get_setting(L"SID", &varStringSid);
  361. ASSERT(varLoginName.vt == VT_BSTR);
  362. if (SUCCEEDED(hrSid))
  363. {
  364. TCHAR szKey[MAX_PATH];
  365. TCHAR szProfilePath[MAX_PATH];
  366. szProfilePath[0] = TEXT('\0');
  367. // First, get the profile path
  368. DWORD dwSize = sizeof(szProfilePath);
  369. if (SUCCEEDED(StringCchCopy(szKey,
  370. ARRAYSIZE(szKey),
  371. TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"))) &&
  372. SUCCEEDED(StringCchCat(szKey,
  373. ARRAYSIZE(szKey),
  374. varStringSid.bstrVal)) &&
  375. (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE,
  376. szKey,
  377. TEXT("ProfileImagePath"),
  378. NULL,
  379. szProfilePath,
  380. &dwSize)))
  381. {
  382. // Reset ACLs on the profile so we can backup files and
  383. // later delete the profile.
  384. EnsureAdminFileAccess(szProfilePath);
  385. // Backup the user's files, if requested
  386. if (varBackupPath.vt == VT_BSTR && varBackupPath.bstrVal && *varBackupPath.bstrVal)
  387. {
  388. WCHAR szPath[MAX_PATH];
  389. hr = _BuildBackupPath(pLogonUser, varLoginName.bstrVal, varBackupPath.bstrVal, szPath);
  390. if (SUCCEEDED(hr))
  391. {
  392. ASSERT(varStringSid.vt == VT_BSTR);
  393. hr = BackupUserData(varStringSid.bstrVal, szProfilePath, szPath);
  394. }
  395. }
  396. }
  397. }
  398. if (SUCCEEDED(hr))
  399. {
  400. nasRet = NetUserDel(NULL, varLoginName.bstrVal);
  401. // NERR_UserNotFound can happen if the account was deleted via
  402. // some other mechanism (e.g. lusrmgr.msc). However, we know
  403. // that the account existed recently, so try to clean up the
  404. // picture, profile, etc. and remove the user from our DPA.
  405. if ((nasRet == NERR_Success) || (nasRet == NERR_UserNotFound))
  406. {
  407. TCHAR szHintKey[MAX_PATH];
  408. int iUserIndex;
  409. // Delete the user's picture if it exists
  410. SHSetUserPicturePath(varLoginName.bstrVal, 0, NULL);
  411. // Delete the user's profile
  412. if (SUCCEEDED(hrSid))
  413. {
  414. ASSERT(varStringSid.vt == VT_BSTR);
  415. DeleteProfile(varStringSid.bstrVal, NULL, NULL);
  416. }
  417. // Delete the user's hint
  418. if (PathCombine(szHintKey, c_szRegRoot, varLoginName.bstrVal))
  419. {
  420. SHDeleteKey(HKEY_LOCAL_MACHINE, szHintKey);
  421. }
  422. // Indicate success
  423. *pbSuccess = VARIANT_TRUE;
  424. hr = S_OK;
  425. // Patch up the list of users
  426. iUserIndex = DPA_GetPtrIndex(_hdpaUsers, pLogonUser);
  427. if ( iUserIndex != -1 )
  428. {
  429. // Release ref held by DPA and remove from DPA
  430. pLogonUser->Release();
  431. DPA_DeletePtr(_hdpaUsers, iUserIndex);
  432. }
  433. else
  434. {
  435. // Invalidate the cached user infomation forcing
  436. // a reenumeration the next time a client uses
  437. // this object
  438. _DestroyHDPAUsers();
  439. }
  440. }
  441. else
  442. {
  443. hr = HRESULT_FROM_WIN32(nasRet);
  444. }
  445. }
  446. pLogonUser->Release();
  447. SysFreeString(varLoginName.bstrVal);
  448. SysFreeString(varStringSid.bstrVal);
  449. }
  450. return hr;
  451. }
  452. HRESULT CLogonEnumUsers::_GetUserByName(BSTR bstrLoginName, ILogonUser** ppLogonUserInfo)
  453. {
  454. HRESULT hr;
  455. INT cUsers, cRet;
  456. ILogonUser *pLogonUser;
  457. VARIANT varLoginName;
  458. int i;
  459. if (!_hdpaUsers)
  460. {
  461. // need to go enumerate all of the users.
  462. hr = _EnumerateUsers();
  463. if (FAILED(hr))
  464. {
  465. TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
  466. return hr;
  467. }
  468. }
  469. cUsers = DPA_GetPtrCount(_hdpaUsers);
  470. hr = E_INVALIDARG;
  471. for (i = 0; i < cUsers; i++)
  472. {
  473. pLogonUser = (ILogonUser*)DPA_FastGetPtr(_hdpaUsers, i);
  474. pLogonUser->get_setting(L"LoginName", &varLoginName);
  475. ASSERT(varLoginName.vt == VT_BSTR);
  476. cRet = StrCmpW(bstrLoginName, varLoginName.bstrVal);
  477. SysFreeString(varLoginName.bstrVal);
  478. if ( cRet == 0 )
  479. {
  480. *ppLogonUserInfo = pLogonUser;
  481. (*ppLogonUserInfo)->AddRef();
  482. hr = S_OK;
  483. break;
  484. }
  485. }
  486. return hr;
  487. }
  488. HRESULT CLogonEnumUsers::_GetUserByIndex(LONG lUserID, ILogonUser** ppLogonUserInfo)
  489. {
  490. HRESULT hr;
  491. int cUsers;
  492. *ppLogonUserInfo = NULL;
  493. if (!_hdpaUsers)
  494. {
  495. // need to go enumerate all of the users.
  496. hr = _EnumerateUsers();
  497. if (FAILED(hr))
  498. {
  499. TraceMsg(TF_WARNING, "CLogonEnumUsers::get_length: failed to create _hdpaUsers!");
  500. return hr;
  501. }
  502. }
  503. cUsers = DPA_GetPtrCount(_hdpaUsers);
  504. if ((cUsers > 0) && (lUserID >= 0) && (lUserID < cUsers))
  505. {
  506. *ppLogonUserInfo = (ILogonUser*)DPA_FastGetPtr(_hdpaUsers, lUserID);
  507. (*ppLogonUserInfo)->AddRef();
  508. hr = S_OK;
  509. }
  510. else
  511. {
  512. hr = E_INVALIDARG;
  513. }
  514. return hr;
  515. }
  516. STDAPI_(int) ReleaseLogonUserCallback(LPVOID pData1, LPVOID pData2)
  517. {
  518. UNREFERENCED_PARAMETER(pData2);
  519. ILogonUser* pUser = (ILogonUser*)pData1;
  520. pUser->Release();
  521. return 1;
  522. }
  523. void CLogonEnumUsers::_DestroyHDPAUsers()
  524. {
  525. HDPA hdpaToFree = (HDPA)InterlockedExchangePointer(reinterpret_cast<void**>(&_hdpaUsers), NULL);
  526. if (hdpaToFree)
  527. {
  528. DPA_DestroyCallback(hdpaToFree, ReleaseLogonUserCallback, 0);
  529. }
  530. }
  531. // creates the _hdpaUsers for each user on the system
  532. HRESULT CLogonEnumUsers::_EnumerateUsers()
  533. {
  534. HRESULT hr = S_FALSE;
  535. NET_API_STATUS nasRet;
  536. GINA_USER_INFORMATION* pgui = NULL;
  537. DWORD dwEntriesRead = 0;
  538. nasRet = ShellGetUserList(FALSE, // don't remove Guest
  539. &dwEntriesRead,
  540. (LPVOID*)&pgui);
  541. if ((nasRet == NERR_Success) || (nasRet == ERROR_MORE_DATA))
  542. {
  543. if (_hdpaUsers)
  544. {
  545. // we have an old data in the dpa and we should dump it and start over
  546. _DestroyHDPAUsers();
  547. }
  548. // create a dpa with spaces for all of the users
  549. _hdpaUsers = DPA_Create(dwEntriesRead);
  550. if (_hdpaUsers)
  551. {
  552. if (dwEntriesRead != 0)
  553. {
  554. GINA_USER_INFORMATION* pguiCurrent;
  555. UINT uEntry;
  556. // cycle through and add each user to the hdpa
  557. for (uEntry = 0, pguiCurrent = pgui; uEntry < dwEntriesRead; uEntry++, pguiCurrent++)
  558. {
  559. CLogonUser* pUser;
  560. if (pguiCurrent->dwFlags & UF_ACCOUNTDISABLE)
  561. {
  562. // skip users whos account is disabled
  563. continue;
  564. }
  565. if (SUCCEEDED(CLogonUser::Create(pguiCurrent->pszName, pguiCurrent->pszFullName, pguiCurrent->pszDomain, IID_ILogonUser, (void**)&pUser)))
  566. {
  567. ASSERT(pUser);
  568. if (DPA_AppendPtr(_hdpaUsers, pUser) != -1)
  569. {
  570. // success! we added this user to the hdpa
  571. hr = S_OK;
  572. }
  573. else
  574. {
  575. TraceMsg(TF_WARNING, "CLogonEnumUsers::_EnumerateUsers: failed to add new user to the DPA!");
  576. pUser->Release();
  577. }
  578. }
  579. }
  580. }
  581. }
  582. else
  583. {
  584. hr = E_OUTOFMEMORY;
  585. }
  586. if (pgui != NULL)
  587. {
  588. LocalFree(pgui);
  589. }
  590. }
  591. else
  592. {
  593. TraceMsg(TF_WARNING, "CLogonEnumUsers::_EnumerateUsers: NetQueryDisplayInformation failed!!");
  594. hr = E_FAIL;
  595. }
  596. return hr;
  597. }
  598. CLogonEnumUsers::CLogonEnumUsers() : _cRef(1), CIDispatchHelper(&IID_ILogonEnumUsers, &LIBID_SHGINALib)
  599. {
  600. DllAddRef();
  601. }
  602. CLogonEnumUsers::~CLogonEnumUsers()
  603. {
  604. ASSERT(_cRef == 0);
  605. _DestroyHDPAUsers();
  606. DllRelease();
  607. }
  608. STDAPI CLogonEnumUsers_Create(REFIID riid, LPVOID* ppv)
  609. {
  610. HRESULT hr = E_OUTOFMEMORY;
  611. CLogonEnumUsers* pEnumUsers = new CLogonEnumUsers;
  612. if (pEnumUsers)
  613. {
  614. hr = pEnumUsers->QueryInterface(riid, ppv);
  615. pEnumUsers->Release();
  616. }
  617. return hr;
  618. }
  619. DWORD LoadHive(HKEY hKey, LPCTSTR pszSubKey, LPCTSTR pszHive)
  620. {
  621. DWORD dwErr;
  622. BOOLEAN bWasEnabled;
  623. NTSTATUS status;
  624. status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &bWasEnabled);
  625. if (NT_SUCCESS(status))
  626. {
  627. dwErr = RegLoadKey(hKey, pszSubKey, pszHive);
  628. RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled);
  629. }
  630. else
  631. {
  632. dwErr = RtlNtStatusToDosError(status);
  633. }
  634. return dwErr;
  635. }
  636. DWORD UnloadHive(HKEY hKey, LPCTSTR pszSubKey)
  637. {
  638. DWORD dwErr;
  639. BOOLEAN bWasEnabled;
  640. NTSTATUS status;
  641. status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &bWasEnabled);
  642. if ( NT_SUCCESS(status) )
  643. {
  644. dwErr = RegUnLoadKey(hKey, pszSubKey);
  645. RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled);
  646. }
  647. else
  648. {
  649. dwErr = RtlNtStatusToDosError(status);
  650. }
  651. return dwErr;
  652. }
  653. void DeleteFilesInTree(LPCTSTR pszDir, LPCTSTR pszFilter)
  654. {
  655. TCHAR szPath[MAX_PATH];
  656. HANDLE hFind;
  657. WIN32_FIND_DATA fd;
  658. // This is best effort only. All errors are ignored
  659. // and no error or success code is returned.
  660. // Look for files matching the filter and delete them
  661. if (PathCombine(szPath, pszDir, pszFilter))
  662. {
  663. hFind = FindFirstFileEx(szPath, FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, 0);
  664. if (hFind != INVALID_HANDLE_VALUE)
  665. {
  666. do
  667. {
  668. if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  669. {
  670. if (PathCombine(szPath, pszDir, fd.cFileName))
  671. {
  672. DeleteFile(szPath);
  673. }
  674. }
  675. }
  676. while (FindNextFile(hFind, &fd));
  677. FindClose(hFind);
  678. }
  679. }
  680. // Look for subdirectories and recurse into them
  681. if (PathCombine(szPath, pszDir, TEXT("*")))
  682. {
  683. hFind = FindFirstFileEx(szPath,
  684. FindExInfoStandard,
  685. &fd,
  686. FindExSearchLimitToDirectories,
  687. NULL,
  688. 0);
  689. if (hFind != INVALID_HANDLE_VALUE)
  690. {
  691. do
  692. {
  693. if (PathIsDotOrDotDot(fd.cFileName))
  694. {
  695. continue;
  696. }
  697. // FindExSearchLimitToDirectories is only an advisory flag,
  698. // so need to check for FILE_ATTRIBUTE_DIRECTORY here.
  699. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
  700. {
  701. if (PathCombine(szPath, pszDir, fd.cFileName))
  702. {
  703. DeleteFilesInTree(szPath, pszFilter);
  704. }
  705. // Expect this to fail if the dir is non-empty
  706. RemoveDirectory(szPath);
  707. }
  708. }
  709. while (FindNextFile(hFind, &fd));
  710. FindClose(hFind);
  711. }
  712. }
  713. }
  714. BOOL
  715. _PathIsEqualOrSubFolder(
  716. LPTSTR pszParent,
  717. LPCTSTR pszSubFolder
  718. )
  719. {
  720. TCHAR szCommon[MAX_PATH];
  721. // PathCommonPrefix() always removes the slash on common
  722. return (pszParent[0] && PathRemoveBackslash(pszParent)
  723. && PathCommonPrefix(pszParent, pszSubFolder, szCommon)
  724. && lstrcmpi(pszParent, szCommon) == 0);
  725. }
  726. HRESULT BackupUserData(LPCTSTR pszSid, LPTSTR pszProfilePath, LPCTSTR pszDestPath)
  727. {
  728. DWORD dwErr;
  729. TCHAR szHive[MAX_PATH];
  730. // We will copy these special folders
  731. const LPCTSTR aValueNames[] =
  732. {
  733. TEXT("Desktop"),
  734. TEXT("Personal"),
  735. TEXT("My Pictures") // must come after Personal
  736. };
  737. if ( pszSid == NULL || *pszSid == TEXT('\0') ||
  738. pszProfilePath == NULL || *pszProfilePath == TEXT('\0') ||
  739. pszDestPath == NULL || *pszDestPath == TEXT('\0') )
  740. {
  741. return E_INVALIDARG;
  742. }
  743. // Before we do anything else, make sure the destination directory
  744. // exists. Create this even if we don't copy any files below, so the
  745. // user sees that something happened.
  746. dwErr = SHCreateDirectoryEx(NULL, pszDestPath, NULL);
  747. if ( dwErr == ERROR_FILE_EXISTS || dwErr == ERROR_ALREADY_EXISTS )
  748. dwErr = ERROR_SUCCESS;
  749. if ( dwErr != ERROR_SUCCESS )
  750. return dwErr;
  751. // Load the user's hive
  752. if (PathCombine(szHive, pszProfilePath, TEXT("ntuser.dat")))
  753. {
  754. dwErr = LoadHive(HKEY_USERS, pszSid, szHive);
  755. }
  756. else
  757. {
  758. dwErr = ERROR_INSUFFICIENT_BUFFER;
  759. }
  760. if ( dwErr == ERROR_SUCCESS )
  761. {
  762. HKEY hkShellFolders = NULL;
  763. TCHAR szKey[MAX_PATH];
  764. // Open the Shell Folders key for the user. We use "Shell Folders"
  765. // here rather than "User Shell Folders" so we don't have to expand
  766. // ENV strings for the user (we don't have a token).
  767. //
  768. // The only way Shell Folders can be out of date is if someone
  769. // changed User Shell Folders since the last time the target user
  770. // logged on, and didn't subsequently call SHGetFolderPath. This
  771. // is a very small risk, but it's possible.
  772. //
  773. // If we encounter problems here, then we will need to build a
  774. // pseudo-environment block for the user containing USERNAME and
  775. // USERPROFILE (at least) so we can switch to User Shell Folders
  776. // and do the ENV substitution.
  777. if (SUCCEEDED(StringCchCopy(szKey, ARRAYSIZE(szKey), pszSid)) &&
  778. SUCCEEDED(StringCchCat(szKey,
  779. ARRAYSIZE(szKey),
  780. TEXT("\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"))))
  781. {
  782. dwErr = RegOpenKeyEx(HKEY_USERS,
  783. szKey,
  784. 0,
  785. KEY_QUERY_VALUE,
  786. &hkShellFolders);
  787. }
  788. else
  789. {
  790. dwErr == ERROR_INSUFFICIENT_BUFFER;
  791. }
  792. if ( dwErr == ERROR_SUCCESS )
  793. {
  794. LPTSTR pszFrom;
  795. LPTSTR pszTo;
  796. // Allocate 2 buffers for double-NULL terminated lists of paths.
  797. // Note that the buffers have 1 extra char (compared to cchFrom
  798. // and cchTo below) and are zero-inited. This extra char ensures
  799. // that the list is double-NULL terminated.
  800. pszFrom = (LPTSTR)LocalAlloc(LPTR, (MAX_PATH+ 1) * ARRAYSIZE(aValueNames) * sizeof(TCHAR));
  801. pszTo = (LPTSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * ARRAYSIZE(aValueNames) * sizeof(TCHAR));
  802. if (pszFrom && pszTo)
  803. {
  804. int i;
  805. // Get each source directory from the registry, build
  806. // a corresponding destination path, and add the paths
  807. // to the lists for SHFileOperation.
  808. for (i = 0; i < ARRAYSIZE(aValueNames); i++)
  809. {
  810. // Copy the source path directly into the list
  811. DWORD dwSize = MAX_PATH * sizeof(TCHAR); // we allocated enough room for each string to be MAX_PATH above
  812. dwErr = RegQueryValueEx(hkShellFolders,
  813. aValueNames[i],
  814. NULL,
  815. NULL,
  816. (LPBYTE)pszFrom,
  817. &dwSize);
  818. if (dwErr == ERROR_SUCCESS)
  819. {
  820. if (!_PathIsEqualOrSubFolder(pszProfilePath, pszFrom))
  821. {
  822. // We only move folders underneath the profile path. The reason for this is
  823. // two fold - first only the profile path will be deleted when the users profile
  824. // is deleted, and second a malicious user could point their mydocs at c:\ and
  825. // this code would be run as an administrator and we would go and try to move/delete
  826. // the contents of the whole drive
  827. continue;
  828. }
  829. // Build a destination path with the same
  830. // leaf name as the source.
  831. PathRemoveBackslash(pszFrom);
  832. LPCTSTR pszDir = PathFindFileName(pszFrom);
  833. if (PathIsFileSpec(pszDir) &&
  834. PathCombine(pszTo, pszDestPath, pszDir))
  835. {
  836. // we sucessfully have the pszFrom->pszTo mapping for this regkey
  837. pszFrom += lstrlen(pszFrom) + 1;
  838. pszTo += lstrlen(pszTo) + 1;
  839. }
  840. }
  841. }
  842. // Did we find anything?
  843. if ((*pszFrom != TEXT('\0') && (*pszTo != TEXT('\0'))))
  844. {
  845. SHFILEOPSTRUCT fo = {0};
  846. fo.hwnd = NULL;
  847. fo.wFunc = FO_MOVE;
  848. fo.pFrom = pszFrom; // should already be double-null terminated (was alloced w/ zero init)
  849. fo.pTo = pszTo; // should already be double-null terminated (was alloced w/ zero init)
  850. fo.fFlags = FOF_MULTIDESTFILES |
  851. FOF_NOCONFIRMATION |
  852. FOF_NOCONFIRMMKDIR |
  853. FOF_NOCOPYSECURITYATTRIBS |
  854. FOF_NOERRORUI |
  855. FOF_RENAMEONCOLLISION;
  856. // Move everything in one shot
  857. dwErr = SHFileOperation(&fo);
  858. // We get ERROR_CANCELLED when My Pictures is contained
  859. // within My Documents, which is the normal case. In this
  860. // case My Pictures is moved along with My Documents and
  861. // doesn't exist any more in the source location when the
  862. // copy engine gets around to moving My Pictures.
  863. //
  864. // We have to continue to specify My Pictures separately
  865. // to account for any cases where it is not contained
  866. // in My Documents, even though that's relatively rare.
  867. //
  868. // Note that putting My Pictures ahead of Personal in
  869. // aValueNames above would avoid the error, but My Pictures
  870. // would no longer be under My Documents after the move.
  871. if (dwErr == ERROR_CANCELLED)
  872. {
  873. dwErr = ERROR_SUCCESS;
  874. }
  875. if (dwErr == ERROR_SUCCESS)
  876. {
  877. // Now go back and delete stuff we didn't really
  878. // want (i.e. shortcut files)
  879. DeleteFilesInTree(pszDestPath, TEXT("*.lnk"));
  880. }
  881. }
  882. }
  883. else
  884. {
  885. dwErr = ERROR_OUTOFMEMORY;
  886. }
  887. if (pszFrom != NULL)
  888. {
  889. LocalFree(pszFrom);
  890. }
  891. if (pszTo != NULL)
  892. {
  893. LocalFree(pszTo);
  894. }
  895. // Close the Shell Folders key
  896. RegCloseKey(hkShellFolders);
  897. }
  898. // Unload the hive
  899. UnloadHive(HKEY_USERS, pszSid);
  900. }
  901. if ( dwErr == ERROR_FILE_NOT_FOUND )
  902. {
  903. // Something was missing, possibly the entire profile (e.g. if the
  904. // user had never logged on), or possibly just one of the Shell Folders
  905. // reg values. It just means there was less work to do.
  906. dwErr = ERROR_SUCCESS;
  907. }
  908. return HRESULT_FROM_WIN32(dwErr);
  909. }
  910. BOOL _SetFileSecurityUsingNTName(LPWSTR pObjectName,
  911. PSECURITY_DESCRIPTOR pSD,
  912. PBOOL pbIsFile)
  913. {
  914. NTSTATUS Status;
  915. UNICODE_STRING usFileName;
  916. OBJECT_ATTRIBUTES Obja;
  917. IO_STATUS_BLOCK IoStatusBlock;
  918. HANDLE hFile = NULL;
  919. Status = RtlInitUnicodeStringEx(&usFileName, pObjectName);
  920. if (NT_SUCCESS(Status))
  921. {
  922. InitializeObjectAttributes(&Obja,
  923. &usFileName,
  924. OBJ_CASE_INSENSITIVE,
  925. NULL,
  926. NULL);
  927. Status = NtOpenFile(&hFile,
  928. WRITE_DAC,
  929. &Obja,
  930. &IoStatusBlock,
  931. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  932. FILE_OPEN_REPARSE_POINT);
  933. if (Status == STATUS_INVALID_PARAMETER)
  934. {
  935. Status = NtOpenFile(&hFile,
  936. WRITE_DAC,
  937. &Obja,
  938. &IoStatusBlock,
  939. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  940. 0);
  941. }
  942. if (NT_SUCCESS(Status))
  943. {
  944. if (!SetKernelObjectSecurity(hFile,
  945. DACL_SECURITY_INFORMATION,
  946. pSD))
  947. {
  948. Status = RtlGetLastNtStatus();
  949. // We successfully opened for WRITE_DAC access, so this shouldn't fail
  950. ASSERT(FALSE);
  951. }
  952. NtClose(hFile);
  953. }
  954. if (NT_SUCCESS(Status))
  955. {
  956. //
  957. // That worked. Now open the file again and read attributes, to see
  958. // if it's a file or directory. Default to File if this fails.
  959. // See comments in _TreeResetCallback below.
  960. //
  961. *pbIsFile = TRUE;
  962. if (NT_SUCCESS(NtOpenFile(&hFile,
  963. FILE_GENERIC_READ,
  964. &Obja,
  965. &IoStatusBlock,
  966. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  967. 0)))
  968. {
  969. //
  970. // Query the attributes for the file/dir.
  971. //
  972. FILE_BASIC_INFORMATION BasicFileInfo;
  973. if (NT_SUCCESS(NtQueryInformationFile(hFile,
  974. &IoStatusBlock,
  975. &BasicFileInfo,
  976. sizeof(BasicFileInfo),
  977. FileBasicInformation)))
  978. {
  979. if (BasicFileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  980. {
  981. *pbIsFile = FALSE;
  982. }
  983. }
  984. NtClose(hFile);
  985. }
  986. }
  987. }
  988. return NT_SUCCESS(Status);
  989. }
  990. void _TreeResetCallback(LPWSTR pObjectName,
  991. DWORD status,
  992. PPROG_INVOKE_SETTING pInvokeSetting,
  993. PVOID pContext,
  994. BOOL bSecuritySet)
  995. {
  996. BOOL bIsFile = TRUE;
  997. // Default is "continue"
  998. *pInvokeSetting = ProgressInvokeEveryObject;
  999. // Stamp the permissions on this object
  1000. _SetFileSecurityUsingNTName(pObjectName, (PSECURITY_DESCRIPTOR)pContext, &bIsFile);
  1001. //
  1002. // bSecuritySet = TRUE means TreeResetNamedSecurityInfo set the owner.
  1003. //
  1004. // status != ERROR_SUCCESS means it couldn't enumerate the child.
  1005. //
  1006. // If it's not a file, retry the operation (with no access initially,
  1007. // TreeResetNamedSecurityInfo can't get attributes and tries to
  1008. // enumerate everything as if it's a directory).
  1009. //
  1010. // Have to be careful to avoid infinite loops here. Basically, we assume
  1011. // everything is a file. If we can grant ourselves access above, we get
  1012. // good attributes and do the right thing. If not, we skip the retry.
  1013. //
  1014. if (bSecuritySet &&
  1015. (status != ERROR_SUCCESS) &&
  1016. !bIsFile)
  1017. {
  1018. *pInvokeSetting = ProgressRetryOperation;
  1019. }
  1020. }
  1021. DWORD EnsureAdminFileAccess(LPTSTR pszPath)
  1022. {
  1023. DWORD dwErr;
  1024. PSECURITY_DESCRIPTOR pSD;
  1025. const TCHAR c_szAdminSD[] = TEXT("O:BAG:BAD:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)");
  1026. if (ConvertStringSecurityDescriptorToSecurityDescriptor(c_szAdminSD, SDDL_REVISION_1, &pSD, NULL))
  1027. {
  1028. PSID pOwner = NULL;
  1029. BOOL bDefault;
  1030. CPrivilegeEnable privilege(SE_TAKE_OWNERSHIP_NAME);
  1031. GetSecurityDescriptorOwner(pSD, &pOwner, &bDefault);
  1032. //
  1033. // When the current user doesn't have any access, we have to do things
  1034. // in the correct order. For each file or directory in the tree,
  1035. // 1. Take ownership, this gives us permission to...
  1036. // 2. Set permissions, this gives us permission to...
  1037. // 3. See if it is a directory, and recurse into it
  1038. //
  1039. // TreeResetNamedSecurityInfo doesn't quite work that way, so we use
  1040. // it to set the owner and do the enumeration. The callback sets
  1041. // the permissions, and tells TreeResetNamedSecurityInfo to retry
  1042. // the enumeration if necessary.
  1043. //
  1044. dwErr = TreeResetNamedSecurityInfo(pszPath,
  1045. SE_FILE_OBJECT,
  1046. OWNER_SECURITY_INFORMATION,
  1047. pOwner,
  1048. NULL,
  1049. NULL,
  1050. NULL,
  1051. FALSE,
  1052. _TreeResetCallback,
  1053. ProgressInvokeEveryObject,
  1054. pSD);
  1055. LocalFree(pSD);
  1056. }
  1057. else
  1058. {
  1059. dwErr = GetLastError();
  1060. }
  1061. return dwErr;
  1062. }