Windows2000/private/windows/shell/dskquota/watchdog/watchdog.cpp
2020-09-30 17:12:32 +02:00

665 lines
20 KiB
C++

/* File: watchdog.cpp
Description: The CWatchDog class is the main control object for the
disk quota watchdog applet. A client merely creates a CWatchDog
and tells it to "Run()".
CWatchDog
To run, the object does the following:
1. Enumerates all local and connected volumes on the machine.
2. For any volumes that support quotas, quota statistics are
gathered for both the volume and the local user on the volume.
Statistics are maintained in a list of CStatistics objects; one
object for each volume/user pair.
3. Once all information has been gathered, a list of action objects
(CActionEmail, CActionPopup) are created. System policy and
previous notification history are considered when creating action
objects.
4. When all action objects are created, they are performed.
Revision History:
Date Description Programmer
--
07/01/97 Initial creation. BrianAu
*/
#include <precomp.hxx>
#pragma hdrstop
#include "action.h"
#include "watchdog.h"
#include "resource.h"
// Required so we can create auto_ptr<BYTE>
struct Byte
{
BYTE b;
};
CWatchDog::CWatchDog(
HANDLE htokenUser
) : m_htokenUser(htokenUser),
m_history(m_policy)
{
// Nothing else to do.
}
CWatchDog::~CWatchDog(
VOID
)
{
ClearActionList();
}
// This is the function that does it all!
// Just create a watchdog object and tell it to run.
HRESULT
CWatchDog::Run(
VOID
)
{
HRESULT hr = NOERROR;
// First check to see if we should do anything based upon the user's
// notification history stored in the registry and the notification
// policy defined by the system administrator. If we've recently
// displayed a popup or sent email, or if the system policy says
// "don't send email or display a popup", we won't go any further.
// No sense doing anything expensive if we don't need to.
if (ShouldDoAnyNotifications())
{
shauto_ptr<ITEMIDLIST> ptrIdlDrives;
oleauto_ptr<IShellFolder> ptrDesktop;
// Bind to the desktop folder.
hr = SHGetDesktopFolder(ptrDesktop._getoutptr());
if (SUCCEEDED(hr))
{
hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, ptrIdlDrives._getoutptr());
if (SUCCEEDED(hr))
{
// Bind to the "Drives" folder.
oleauto_ptr<IShellFolder> ptrDrives;
hr = ptrDesktop->BindToObject(ptrIdlDrives, NULL, IID_IShellFolder, (LPVOID *)ptrDrives._getoutptr());
if (SUCCEEDED(hr))
{
// Gather quota statistics for local and connected drives.
hr = GatherQuotaStatistics(ptrDrives);
if (SUCCEEDED(hr) && (0 < m_statsList.Count()))
{
// Build up the list of actions required.
hr = BuildActionList();
if (SUCCEEDED(hr) && (0 < m_actionList.Count()))
{
// We have a list of actions. Do them.
hr = DoActions();
}
}
}
}
}
}
return hr;
}
// Gather up all of the quota statistics for the volumes in the shell's "Drives"
// folder and the quota statistics on those volumes for the current user if
// applicable.
HRESULT
CWatchDog::GatherQuotaStatistics(
IShellFolder *psfDrives
)
{
HRESULT hr;
oleauto_ptr<IEnumIDList> ptrEnum;
// Enumerate all of the non-folder objects in the drives folder.
hr = psfDrives->EnumObjects(NULL, SHCONTF_NONFOLDERS, ptrEnum._getoutptr());
if (SUCCEEDED(hr))
{
// Convert the user's token to a SID.
auto_ptr<Byte> ptrSid = (Byte *)GetUserSid(m_htokenUser);
if (NULL != (Byte *)ptrSid)
{
shauto_ptr<ITEMIDLIST> ptrIdlItem;
ULONG ulFetched = 0;
// For each item in the drives folder...
while(S_OK == ptrEnum->Next(1, ptrIdlItem._getoutptr(), &ulFetched))
{
TCHAR szName[MAX_PATH];
TCHAR szDisplayName[MAX_PATH];
StrRet strretName((LPITEMIDLIST)ptrIdlItem);
StrRet strretDisplayName((LPITEMIDLIST)ptrIdlItem);
// Get the non-display name form; i.e. "G:\"
hr = psfDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_FORPARSING, &strretName);
if (SUCCEEDED(hr))
{
strretName.GetString(szName, ARRAYSIZE(szName));
// Protect against getting something like "Dial-up Networking"
// and thinking it's drive 'D'.
if (TEXT(':') == szName[1])
{
// Get the display name form; i.e. "My Disk (G:)"
psfDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_NORMAL, &strretDisplayName);
strretDisplayName.GetString(szDisplayName, ARRAYSIZE(szDisplayName));
// Add the volume/user pair to the statistics list.
// This function does a lot of things.
// 1. Checks to see if volume supports quotas.
// 2. Gets volume quota information.
// 3. Gets user quota information for this volume.
// If something fails or the volume doesn't support quotas,
// the entry is not added to the list and will therefore not
// participate in any quota notification actions.
m_statsList.AddEntry(szName[0], szDisplayName, (LPBYTE)((Byte *)ptrSid));
}
}
}
}
}
return hr;
}
// Build up the list of actions from our list of volume/user statistics.
// Note that it's important we build the email actions before the popup
// dialog actions. Actions are appended to the action list. The popup
// dialog used in popup actions may be a modal dialog which will block
// the call to DoAction() until the user responds. If the user is away
// from the computer for a while, this will prevent any pending email
// notifications from going out. Therefore, we always want to get the
// email notifications out of the way before we handle any notifications
// that might require user intervention.
HRESULT
CWatchDog::BuildActionList(
VOID
)
{
HRESULT hr = NOERROR;
// Clear out any previous action objects.
ClearActionList();
if (ShouldSendEmail())
{
// Build up any actions requiring email.
// Important that email comes before popup dialogs in action list.
// See note in function header for details.
BuildEmailActions();
}
if (ShouldPopupDialog())
{
// Build up any actions requiring popup dialogs.
BuildPopupDialogActions();
}
return hr;
}
// Build up all of the required email messages, appending them to the action
// list. This is where all of the email message formatting is done.
// Currently, it's all done in this one function. If more sophisticated
// email is required at a later date, this function may need to be
// divided into more functions with this as the central entry point.
HRESULT
CWatchDog::BuildEmailActions(
VOID
)
{
HRESULT hr = NOERROR;
INT cStatsEntries = m_statsList.Count();
if (0 < cStatsEntries)
{
// Now we know we'll need MAPI. Go ahead and initialize the
// session. This will:
// 1. Load MAPI32.DLL.
// 2. Logon to MAPI creating a MAPI session.
// 3. Obtain a pointer to the outbox.
// Note that MAPI will be uninitialized and MAPI32.DLL will be
// unloaded when the session object is destroyed.
hr = m_mapiSession.Initialize();
if (SUCCEEDED(hr))
{
// Get the outbox object interface pointer.
LPMAPIFOLDER pOutBoxFolder;
hr = m_mapiSession.GetOutBoxFolder(&pOutBoxFolder);
if (SUCCEEDED(hr))
{
// OK. Now we have loaded MAPI, created a MAPI session
// and we have a pointer to the outbox folder. Now we can
// create messages and send them.
// These are the pieces of the text message.
CMapiMessageBody body; // Message body text.
// Add the canned header to the email message.
body.Append(g_hInstDll, IDS_EMAIL_HEADER);
// Subject line contains the computer name.
CString strComputerName;
DWORD cchComputerName = MAX_COMPUTERNAME_LENGTH + 1;
GetComputerName(strComputerName.GetBuffer(cchComputerName),
&cchComputerName);
CString strSubject(g_hInstDll, IDS_EMAIL_SUBJECT_LINE, (LPCTSTR)strComputerName);
CString strVolume(g_hInstDll, IDS_EMAIL_VOLUME);
CString strWarning(g_hInstDll, IDS_EMAIL_WARNING);
CString strLimit(g_hInstDll, IDS_EMAIL_LIMIT);
CString strUsed(g_hInstDll, IDS_EMAIL_USED);
INT cEmailMsgEntries = 0;
// Enumerate all of the entries in the statistics list.
// Each entry contains information for a volume/user pair.
//
for (INT i = 0; i < cStatsEntries && SUCCEEDED(hr); i++)
{
// Retrieve the next entry from the list.
const CStatistics *pStats = m_statsList.GetEntry(i);
Assert(NULL != pStats);
// Do these stats require reporting? Some reasons
// why not...
// 1. Volume doesn't support quotas
// (non-NTFS, non-NTFS 5.0)
// 2. No access to volume. Can't open it.
// 3. "Warn user over threshold" bit on the volume
// is not set.
// 4. User's quota usage is below threshold value.
//
if (pStats->IncludeInReport())
{
// Now create the message text for the volume
// in this statistics object.
TCHAR szBytes[40];
TCHAR szBytesOver[40];
// "Location: MyShare on MyDisk (C:)"
LPCTSTR pszVolDisplayName = pStats->GetVolumeDisplayName() ?
pStats->GetVolumeDisplayName() :
TEXT("");
body.Append(g_hInstDll,
IDS_EMAIL_FMT_VOLUME,
(LPCTSTR)strVolume,
pszVolDisplayName);
// "Quota Used: 91MB (1MB over warning)"
XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaUsed().QuadPart,
szBytes, ARRAYSIZE(szBytes));
// Format and add the "1MB" part of "(1MB over warning)".
__int64 diff = pStats->GetUserQuotaUsed().QuadPart - pStats->GetUserQuotaThreshold().QuadPart;
if (0 > diff)
{
diff = 0;
}
XBytes::FormatByteCountForDisplay(diff, szBytesOver, ARRAYSIZE(szBytesOver));
body.Append(g_hInstDll,
IDS_EMAIL_FMT_USED,
(LPCTSTR)strUsed,
szBytes,
szBytesOver);
// "Warning Level: 90MB"
XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaThreshold().QuadPart,
szBytes, ARRAYSIZE(szBytes));
body.Append(g_hInstDll,
IDS_EMAIL_FMT_WARNING,
(LPCTSTR)strWarning,
szBytes);
// "Quota Limit: 100MB"
XBytes::FormatByteCountForDisplay(pStats->GetUserQuotaLimit().QuadPart,
szBytes, ARRAYSIZE(szBytes));
body.Append(g_hInstDll,
IDS_EMAIL_FMT_LIMIT,
(LPCTSTR)strLimit,
szBytes);
if (pStats->DenyAtLimit())
{
// Volume is set to deny writes at the quota limit.
// Add a reminder of this fact.
body.Append(g_hInstDll, IDS_EMAIL_DENY_AT_LIMIT);
}
// Keep track of how many volume entries we've added to
// the message. If the count is 0 when we're done, no sense
// in creating/sending the message.
cEmailMsgEntries++;
}
}
if (0 < cEmailMsgEntries)
{
// Create an email action object from the text
// we've formatted above.
CAction *pAction = new CActionEmail(m_mapiSession,
pOutBoxFolder,
m_policy.GetOtherEmailTo(),
m_policy.GetOtherEmailCc(),
m_policy.GetOtherEmailBcc(),
strSubject,
body);
if (NULL != pAction)
{
// Add the action object to the list of action objects.
m_actionList.Append(pAction);
}
}
pOutBoxFolder->Release();
}
else
{
DebugMsg(DM_ERROR, TEXT("CWatchDog::BuildEmailActions: Error 0x%08X getting MAPI outbox"), hr);
}
}
else
{
DebugMsg(DM_ERROR, TEXT("CWatchDog::BuildEmailActions: Error 0x%08X initializing MAPI session"), hr);
}
}
return hr;
}
// Build up all of the required popup actions, appending them to the action
// list.
HRESULT
CWatchDog::BuildPopupDialogActions(
VOID
)
{
HRESULT hr = NOERROR;
// Create a dialog popup action object.
// It uses the statistics list to fill it's listview
// control. Other than that, it's fully self-contained.
// Note however that we're just passing a reference
// to the statistics list and the CActionPopup object doesn't
// create a copy of the object. Therefore, the statistics
// list object MUST live longer than the popup object.
// This is a safe assumption in the current design.
CAction *pAction = new CActionPopup(m_statsList);
if (NULL != pAction)
{
// Add the action object to the list of action objects.
m_actionList.Append(pAction);
}
return hr;
}
// Remove all actions from the action list.
VOID
CWatchDog::ClearActionList(
VOID
)
{
INT cActions = m_actionList.Count();
for (INT i = 0; i < cActions; i++)
{
delete m_actionList[i];
}
m_actionList.Clear();
}
// Perform the actions in the action list.
HRESULT
CWatchDog::DoActions(
VOID
)
{
HRESULT hr = NOERROR;
INT cActions = m_actionList.Count();
// For each action in the action list...
for (INT i = 0; i < cActions; i++)
{
Assert(NULL != m_actionList[i]);
// Do the action.
// Pass in a reference to our CHistory object so that the
// action object can update the history record appropriately.
hr = m_actionList[i]->DoAction(m_history);
}
return hr;
}
// Convert a user's token handle into a SID.
// Caller is responsible for calling delete[] on the returned buffer when
// done with it.
LPBYTE
CWatchDog::GetUserSid(
HANDLE htokenUser
)
{
LPBYTE pbUserSid = NULL;
BOOL bResult = FALSE;
if (NULL != htokenUser)
{
// Note the use of "Byte" and not "BYTE". auto_ptr<> requires
// a class, struct or union. No scalar types.
auto_ptr<Byte> ptrUserToken = NULL;
DWORD cbUserToken = 256;
// Start with a 256-byte buffer.
ptrUserToken = new Byte[cbUserToken];
if (NULL != (Byte *)ptrUserToken)
{
// Get the user's token information from the system.
DWORD cbUserTokenReqd;
bResult = ::GetTokenInformation(htokenUser,
TokenUser,
ptrUserToken,
cbUserToken,
&cbUserTokenReqd);
if (!bResult && (cbUserTokenReqd > cbUserToken))
{
// Buffer wasn't large enough. Try again
// with the size value returned from the previous call.
// ptrUserToken is an auto_ptr so the original buffer
// is automagically deleted.
ptrUserToken = new Byte[cbUserTokenReqd];
if (NULL != (Byte *)ptrUserToken)
{
bResult = ::GetTokenInformation(htokenUser,
TokenUser,
ptrUserToken,
cbUserTokenReqd,
&cbUserTokenReqd);
}
}
}
if (bResult)
{
TOKEN_USER *pToken = (TOKEN_USER *)((Byte *)ptrUserToken);
if (::IsValidSid(pToken->User.Sid))
{
// If the SID is valid, create a new buffer and copy
// the SID bytes to it. We'll return the new buffer
// to the caller.
INT cbUserSid = ::GetLengthSid(pToken->User.Sid);
pbUserSid = new BYTE[cbUserSid];
if (NULL != pbUserSid)
{
::CopySid(cbUserSid, pbUserSid, pToken->User.Sid);
}
}
else
{
DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - Invalid SID for user token 0x%08X"), htokenUser);
}
}
else
{
DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - GetTokenInformation failed with error 0x%08X"), GetLastError());
}
}
else
{
DebugMsg(DM_ERROR, TEXT("CWatchDog::GetUserSid - OpenThreadToken failed with error 0x%08X"), GetLastError());
}
return pbUserSid;
}
// Should we send email based on policy/history?
BOOL
CWatchDog::ShouldSendEmail(
VOID
)
{
return m_policy.ShouldSendEmail() &&
m_history.ShouldSendEmail();
}
// Should we show a popup based on policy/history?
BOOL
CWatchDog::ShouldPopupDialog(
VOID
)
{
return m_policy.ShouldPopupDialog() &&
m_history.ShouldPopupDialog();
}
// Should we do anything based on policy/history?
BOOL
CWatchDog::ShouldDoAnyNotifications(
VOID
)
{
return m_policy.ShouldDoAnyNotifications() &&
m_history.ShouldDoAnyNotifications();
}