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.

664 lines
22 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. /* File: watchdog.cpp
  3. Description: The CWatchDog class is the main control object for the
  4. disk quota watchdog applet. A client merely creates a CWatchDog
  5. and tells it to "Run()".
  6. CWatchDog
  7. To run, the object does the following:
  8. 1. Enumerates all local and connected volumes on the machine.
  9. 2. For any volumes that support quotas, quota statistics are
  10. gathered for both the volume and the local user on the volume.
  11. Statistics are maintained in a list of CStatistics objects; one
  12. object for each volume/user pair.
  13. 3. Once all information has been gathered, a list of action objects
  14. (CActionEmail, CActionPopup) are created. System policy and
  15. previous notification history are considered when creating action
  16. objects.
  17. 4. When all action objects are created, they are performed.
  18. Revision History:
  19. Date Description Programmer
  20. -------- --------------------------------------------------- ----------
  21. 07/01/97 Initial creation. BrianAu
  22. */
  23. ///////////////////////////////////////////////////////////////////////////////
  24. #include <precomp.hxx>
  25. #pragma hdrstop
  26. #include "action.h"
  27. #include "watchdog.h"
  28. #include "resource.h"
  29. //
  30. // Required so we can create auto_ptr<BYTE>
  31. //
  32. struct Byte
  33. {
  34. BYTE b;
  35. };
  36. CWatchDog::CWatchDog(
  37. HANDLE htokenUser
  38. ) : m_htokenUser(htokenUser),
  39. m_history(m_policy)
  40. {
  41. //
  42. // Nothing else to do.
  43. //
  44. }
  45. CWatchDog::~CWatchDog(
  46. VOID
  47. )
  48. {
  49. ClearActionList();
  50. }
  51. //
  52. // This is the function that does it all!
  53. // Just create a watchdog object and tell it to run.
  54. //
  55. HRESULT
  56. CWatchDog::Run(
  57. VOID
  58. )
  59. {
  60. HRESULT hr = NOERROR;
  61. //
  62. // First check to see if we should do anything based upon the user's
  63. // notification history stored in the registry and the notification
  64. // policy defined by the system administrator. If we've recently
  65. // displayed a popup or sent email, or if the system policy says
  66. // "don't send email or display a popup", we won't go any further.
  67. // No sense doing anything expensive if we don't need to.
  68. //
  69. if (ShouldDoAnyNotifications())
  70. {
  71. shauto_ptr<ITEMIDLIST> ptrIdlDrives;
  72. oleauto_ptr<IShellFolder> ptrDesktop;
  73. //
  74. // Bind to the desktop folder.
  75. //
  76. hr = SHGetDesktopFolder(ptrDesktop._getoutptr());
  77. if (SUCCEEDED(hr))
  78. {
  79. hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, ptrIdlDrives._getoutptr());
  80. if (SUCCEEDED(hr))
  81. {
  82. //
  83. // Bind to the "Drives" folder.
  84. //
  85. oleauto_ptr<IShellFolder> ptrDrives;
  86. hr = ptrDesktop->BindToObject(ptrIdlDrives, NULL, IID_IShellFolder, (LPVOID *)ptrDrives._getoutptr());
  87. if (SUCCEEDED(hr))
  88. {
  89. //
  90. // Gather quota statistics for local and connected drives.
  91. //
  92. hr = GatherQuotaStatistics(ptrDrives);
  93. if (SUCCEEDED(hr) && (0 < m_statsList.Count()))
  94. {
  95. //
  96. // Build up the list of actions required.
  97. //
  98. hr = BuildActionList();
  99. if (SUCCEEDED(hr) && (0 < m_actionList.Count()))
  100. {
  101. //
  102. // We have a list of actions. Do them.
  103. //
  104. hr = DoActions();
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. return hr;
  112. }
  113. //
  114. // Gather up all of the quota statistics for the volumes in the shell's "Drives"
  115. // folder and the quota statistics on those volumes for the current user if
  116. // applicable.
  117. //
  118. HRESULT
  119. CWatchDog::GatherQuotaStatistics(
  120. IShellFolder *psfDrives
  121. )
  122. {
  123. HRESULT hr;
  124. oleauto_ptr<IEnumIDList> ptrEnum;
  125. //
  126. // Enumerate all of the non-folder objects in the drives folder.
  127. //
  128. hr = psfDrives->EnumObjects(NULL, SHCONTF_NONFOLDERS, ptrEnum._getoutptr());
  129. if (SUCCEEDED(hr))
  130. {
  131. //
  132. // Convert the user's token to a SID.
  133. //
  134. auto_ptr<Byte> ptrSid = (Byte *)GetUserSid(m_htokenUser);
  135. if (NULL != (Byte *)ptrSid)
  136. {
  137. shauto_ptr<ITEMIDLIST> ptrIdlItem;
  138. ULONG ulFetched = 0;
  139. //
  140. // For each item in the drives folder...
  141. //
  142. while(S_OK == ptrEnum->Next(1, ptrIdlItem._getoutptr(), &ulFetched))
  143. {
  144. TCHAR szName[MAX_PATH];
  145. TCHAR szDisplayName[MAX_PATH];
  146. StrRet strretName((LPITEMIDLIST)ptrIdlItem);
  147. StrRet strretDisplayName((LPITEMIDLIST)ptrIdlItem);
  148. //
  149. // Get the non-display name form; i.e. "G:\"
  150. //
  151. hr = psfDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_FORPARSING, &strretName);
  152. if (SUCCEEDED(hr))
  153. {
  154. strretName.GetString(szName, ARRAYSIZE(szName));
  155. //
  156. // Protect against getting something like "Dial-up Networking"
  157. // and thinking it's drive 'D'.
  158. //
  159. if (TEXT(':') == szName[1])
  160. {
  161. //
  162. // Get the display name form; i.e. "My Disk (G:)"
  163. //
  164. psfDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_NORMAL, &strretDisplayName);
  165. strretDisplayName.GetString(szDisplayName, ARRAYSIZE(szDisplayName));
  166. //
  167. // Add the volume/user pair to the statistics list.
  168. // This function does a lot of things.
  169. // 1. Checks to see if volume supports quotas.
  170. // 2. Gets volume quota information.
  171. // 3. Gets user quota information for this volume.
  172. //
  173. // If something fails or the volume doesn't support quotas,
  174. // the entry is not added to the list and will therefore not
  175. // participate in any quota notification actions.
  176. //
  177. m_statsList.AddEntry(szName[0], szDisplayName, (LPBYTE)((Byte *)ptrSid));
  178. }
  179. }
  180. }
  181. }
  182. }
  183. return hr;
  184. }
  185. //
  186. // Build up the list of actions from our list of volume/user statistics.
  187. // Note that it's important we build the email actions before the popup
  188. // dialog actions. Actions are appended to the action list. The popup
  189. // dialog used in popup actions may be a modal dialog which will block
  190. // the call to DoAction() until the user responds. If the user is away
  191. // from the computer for a while, this will prevent any pending email
  192. // notifications from going out. Therefore, we always want to get the
  193. // email notifications out of the way before we handle any notifications
  194. // that might require user intervention.
  195. //
  196. HRESULT
  197. CWatchDog::BuildActionList(
  198. VOID
  199. )
  200. {
  201. HRESULT hr = NOERROR;
  202. //
  203. // Clear out any previous action objects.
  204. //
  205. ClearActionList();
  206. if (ShouldSendEmail())
  207. {
  208. //
  209. // Build up any actions requiring email.
  210. // Important that email comes before popup dialogs in action list.
  211. // See note in function header for details.
  212. //
  213. BuildEmailActions();
  214. }
  215. if (ShouldPopupDialog())
  216. {
  217. //
  218. // Build up any actions requiring popup dialogs.
  219. //
  220. BuildPopupDialogActions();
  221. }
  222. return hr;
  223. }
  224. //
  225. // Build up all of the required email messages, appending them to the action
  226. // list. This is where all of the email message formatting is done.
  227. // Currently, it's all done in this one function. If more sophisticated
  228. // email is required at a later date, this function may need to be
  229. // divided into more functions with this as the central entry point.
  230. //
  231. HRESULT
  232. CWatchDog::BuildEmailActions(
  233. VOID
  234. )
  235. {
  236. HRESULT hr = NOERROR;
  237. INT cStatsEntries = m_statsList.Count();
  238. if (0 < cStatsEntries)
  239. {
  240. //
  241. // Now we know we'll need MAPI. Go ahead and initialize the
  242. // session. This will:
  243. // 1. Load MAPI32.DLL.
  244. // 2. Logon to MAPI creating a MAPI session.
  245. // 3. Obtain a pointer to the outbox.
  246. //
  247. // Note that MAPI will be uninitialized and MAPI32.DLL will be
  248. // unloaded when the session object is destroyed.
  249. //
  250. hr = m_mapiSession.Initialize();
  251. if (SUCCEEDED(hr))
  252. {
  253. //
  254. // Get the outbox object interface pointer.
  255. //
  256. LPMAPIFOLDER pOutBoxFolder;
  257. hr = m_mapiSession.GetOutBoxFolder(&pOutBoxFolder);
  258. if (SUCCEEDED(hr))
  259. {
  260. //
  261. // OK. Now we have loaded MAPI, created a MAPI session
  262. // and we have a pointer to the outbox folder. Now we can
  263. // create messages and send them.
  264. //
  265. //
  266. // These are the pieces of the text message.
  267. //
  268. CMapiMessageBody body; // Message body text.
  269. //
  270. // Add the canned header to the email message.
  271. //
  272. body.Append(g_hInstDll, IDS_EMAIL_HEADER);
  273. //
  274. // Subject line contains the computer name.
  275. //
  276. CString strComputerName;
  277. DWORD cchComputerName = MAX_COMPUTERNAME_LENGTH + 1;
  278. GetComputerName(strComputerName.GetBuffer(cchComputerName),
  279. &cchComputerName);
  280. CString strSubject(g_hInstDll, IDS_EMAIL_SUBJECT_LINE, (LPCTSTR)strComputerName);
  281. CString strVolume(g_hInstDll, IDS_EMAIL_VOLUME);
  282. CString strWarning(g_hInstDll, IDS_EMAIL_WARNING);
  283. CString strLimit(g_hInstDll, IDS_EMAIL_LIMIT);
  284. CString strUsed(g_hInstDll, IDS_EMAIL_USED);
  285. INT cEmailMsgEntries = 0;
  286. //
  287. // Enumerate all of the entries in the statistics list.
  288. // Each entry contains information for a volume/user pair.
  289. //
  290. for (INT i = 0; i < cStatsEntries && SUCCEEDED(hr); i++)
  291. {
  292. //
  293. // Retrieve the next entry from the list.
  294. //
  295. const CStatistics *pStats = m_statsList.GetEntry(i);
  296. Assert(NULL != pStats);
  297. //
  298. // Do these stats require reporting? Some reasons
  299. // why not...
  300. // 1. Volume doesn't support quotas
  301. // (non-NTFS, non-NTFS 5.0)
  302. // 2. No access to volume. Can't open it.
  303. // 3. "Warn user over threshold" bit on the volume
  304. // is not set.
  305. // 4. User's quota usage is below threshold value.
  306. //
  307. if (pStats->IncludeInReport())
  308. {
  309. //
  310. // Now create the message text for the volume
  311. // in this statistics object.
  312. //
  313. TCHAR szBytes[40];
  314. TCHAR szBytesOver[40];
  315. //
  316. // "Location: MyShare on MyDisk (C:)"
  317. //
  318. LPCTSTR pszVolDisplayName = pStats->GetVolumeDisplayName() ?
  319. pStats->GetVolumeDisplayName() :
  320. TEXT("");
  321. body.Append(g_hInstDll,
  322. IDS_EMAIL_FMT_VOLUME,
  323. (LPCTSTR)strVolume,
  324. pszVolDisplayName);
  325. //
  326. // "Quota Used: 91MB (1MB over warning)"
  327. //
  328. XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaUsed().QuadPart,
  329. szBytes, ARRAYSIZE(szBytes));
  330. //
  331. // Format and add the "1MB" part of "(1MB over warning)".
  332. //
  333. __int64 diff = pStats->GetUserQuotaUsed().QuadPart - pStats->GetUserQuotaThreshold().QuadPart;
  334. if (0 > diff)
  335. {
  336. diff = 0;
  337. }
  338. XBytes::FormatByteCountForDisplay(diff, szBytesOver, ARRAYSIZE(szBytesOver));
  339. body.Append(g_hInstDll,
  340. IDS_EMAIL_FMT_USED,
  341. (LPCTSTR)strUsed,
  342. szBytes,
  343. szBytesOver);
  344. //
  345. // "Warning Level: 90MB"
  346. //
  347. XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaThreshold().QuadPart,
  348. szBytes, ARRAYSIZE(szBytes));
  349. body.Append(g_hInstDll,
  350. IDS_EMAIL_FMT_WARNING,
  351. (LPCTSTR)strWarning,
  352. szBytes);
  353. //
  354. // "Quota Limit: 100MB"
  355. //
  356. XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaLimit().QuadPart,
  357. szBytes, ARRAYSIZE(szBytes));
  358. body.Append(g_hInstDll,
  359. IDS_EMAIL_FMT_LIMIT,
  360. (LPCTSTR)strLimit,
  361. szBytes);
  362. if (pStats->DenyAtLimit())
  363. {
  364. //
  365. // Volume is set to deny writes at the quota limit.
  366. // Add a reminder of this fact.
  367. //
  368. body.Append(g_hInstDll, IDS_EMAIL_DENY_AT_LIMIT);
  369. }
  370. //
  371. // Keep track of how many volume entries we've added to
  372. // the message. If the count is 0 when we're done, no sense
  373. // in creating/sending the message.
  374. //
  375. cEmailMsgEntries++;
  376. }
  377. }
  378. if (0 < cEmailMsgEntries)
  379. {
  380. //
  381. // Create an email action object from the text
  382. // we've formatted above.
  383. //
  384. CAction *pAction = new CActionEmail(m_mapiSession,
  385. pOutBoxFolder,
  386. m_policy.GetOtherEmailTo(),
  387. m_policy.GetOtherEmailCc(),
  388. m_policy.GetOtherEmailBcc(),
  389. strSubject,
  390. body);
  391. if (NULL != pAction)
  392. {
  393. //
  394. // Add the action object to the list of action objects.
  395. //
  396. m_actionList.Append(pAction);
  397. }
  398. }
  399. pOutBoxFolder->Release();
  400. }
  401. else
  402. {
  403. DebugMsg(DM_ERROR, TEXT("CWatchDog::BuildEmailActions: Error 0x%08X getting MAPI outbox"), hr);
  404. }
  405. }
  406. else
  407. {
  408. DebugMsg(DM_ERROR, TEXT("CWatchDog::BuildEmailActions: Error 0x%08X initializing MAPI session"), hr);
  409. }
  410. }
  411. return hr;
  412. }
  413. //
  414. // Build up all of the required popup actions, appending them to the action
  415. // list.
  416. //
  417. HRESULT
  418. CWatchDog::BuildPopupDialogActions(
  419. VOID
  420. )
  421. {
  422. HRESULT hr = NOERROR;
  423. //
  424. // Create a dialog popup action object.
  425. // It uses the statistics list to fill it's listview
  426. // control. Other than that, it's fully self-contained.
  427. // Note however that we're just passing a reference
  428. // to the statistics list and the CActionPopup object doesn't
  429. // create a copy of the object. Therefore, the statistics
  430. // list object MUST live longer than the popup object.
  431. // This is a safe assumption in the current design.
  432. //
  433. CAction *pAction = new CActionPopup(m_statsList);
  434. if (NULL != pAction)
  435. {
  436. //
  437. // Add the action object to the list of action objects.
  438. //
  439. m_actionList.Append(pAction);
  440. }
  441. return hr;
  442. }
  443. //
  444. // Remove all actions from the action list.
  445. //
  446. VOID
  447. CWatchDog::ClearActionList(
  448. VOID
  449. )
  450. {
  451. INT cActions = m_actionList.Count();
  452. for (INT i = 0; i < cActions; i++)
  453. {
  454. delete m_actionList[i];
  455. }
  456. m_actionList.Clear();
  457. }
  458. //
  459. // Perform the actions in the action list.
  460. //
  461. HRESULT
  462. CWatchDog::DoActions(
  463. VOID
  464. )
  465. {
  466. HRESULT hr = NOERROR;
  467. INT cActions = m_actionList.Count();
  468. //
  469. // For each action in the action list...
  470. //
  471. for (INT i = 0; i < cActions; i++)
  472. {
  473. Assert(NULL != m_actionList[i]);
  474. //
  475. // Do the action.
  476. // Pass in a reference to our CHistory object so that the
  477. // action object can update the history record appropriately.
  478. //
  479. hr = m_actionList[i]->DoAction(m_history);
  480. }
  481. return hr;
  482. }
  483. //
  484. // Convert a user's token handle into a SID.
  485. // Caller is responsible for calling delete[] on the returned buffer when
  486. // done with it.
  487. //
  488. LPBYTE
  489. CWatchDog::GetUserSid(
  490. HANDLE htokenUser
  491. )
  492. {
  493. LPBYTE pbUserSid = NULL;
  494. BOOL bResult = FALSE;
  495. if (NULL != htokenUser)
  496. {
  497. //
  498. // Note the use of "Byte" and not "BYTE". auto_ptr<> requires
  499. // a class, struct or union. No scalar types.
  500. //
  501. auto_ptr<Byte> ptrUserToken = NULL;
  502. DWORD cbUserToken = 256;
  503. //
  504. // Start with a 256-byte buffer.
  505. //
  506. ptrUserToken = new Byte[cbUserToken];
  507. if (NULL != (Byte *)ptrUserToken)
  508. {
  509. //
  510. // Get the user's token information from the system.
  511. //
  512. DWORD cbUserTokenReqd;
  513. bResult = ::GetTokenInformation(htokenUser,
  514. TokenUser,
  515. ptrUserToken,
  516. cbUserToken,
  517. &cbUserTokenReqd);
  518. if (!bResult && (cbUserTokenReqd > cbUserToken))
  519. {
  520. //
  521. // Buffer wasn't large enough. Try again
  522. // with the size value returned from the previous call.
  523. // ptrUserToken is an auto_ptr so the original buffer
  524. // is automagically deleted.
  525. //
  526. ptrUserToken = new Byte[cbUserTokenReqd];
  527. if (NULL != (Byte *)ptrUserToken)
  528. {
  529. bResult = ::GetTokenInformation(htokenUser,
  530. TokenUser,
  531. ptrUserToken,
  532. cbUserTokenReqd,
  533. &cbUserTokenReqd);
  534. }
  535. }
  536. }
  537. if (bResult)
  538. {
  539. TOKEN_USER *pToken = (TOKEN_USER *)((Byte *)ptrUserToken);
  540. if (::IsValidSid(pToken->User.Sid))
  541. {
  542. //
  543. // If the SID is valid, create a new buffer and copy
  544. // the SID bytes to it. We'll return the new buffer
  545. // to the caller.
  546. //
  547. INT cbUserSid = ::GetLengthSid(pToken->User.Sid);
  548. pbUserSid = new BYTE[cbUserSid];
  549. if (NULL != pbUserSid)
  550. {
  551. ::CopySid(cbUserSid, pbUserSid, pToken->User.Sid);
  552. }
  553. }
  554. else
  555. {
  556. DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - Invalid SID for user token 0x%08X"), htokenUser);
  557. }
  558. }
  559. else
  560. {
  561. DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - GetTokenInformation failed with error 0x%08X"), GetLastError());
  562. }
  563. }
  564. else
  565. {
  566. DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - OpenThreadToken failed with error 0x%08X"), GetLastError());
  567. }
  568. return pbUserSid;
  569. }
  570. //
  571. // Should we send email based on policy/history?
  572. //
  573. BOOL
  574. CWatchDog::ShouldSendEmail(
  575. VOID
  576. )
  577. {
  578. return m_policy.ShouldSendEmail() &&
  579. m_history.ShouldSendEmail();
  580. }
  581. //
  582. // Should we show a popup based on policy/history?
  583. //
  584. BOOL
  585. CWatchDog::ShouldPopupDialog(
  586. VOID
  587. )
  588. {
  589. return m_policy.ShouldPopupDialog() &&
  590. m_history.ShouldPopupDialog();
  591. }
  592. //
  593. // Should we do anything based on policy/history?
  594. //
  595. BOOL
  596. CWatchDog::ShouldDoAnyNotifications(
  597. VOID
  598. )
  599. {
  600. return m_policy.ShouldDoAnyNotifications() &&
  601. m_history.ShouldDoAnyNotifications();
  602. }