Source code of Windows XP (NT5)
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.

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