|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 2000
//
// File: nopin.cpp
//
//--------------------------------------------------------------------------
#include "pch.h"
#pragma hdrstop
#include "eventlog.h"
#include "nopin.h"
#include "strings.h"
#include "msg.h"
//--------------------------------------------------------------------------
// class CNoPinList
//--------------------------------------------------------------------------
CNoPinList::CNoPinList( void ) : m_pRoot(NULL) { }
CNoPinList::~CNoPinList( void ) { delete m_pRoot; }
//
// Searches the tree for a COMPLETE path that is a subpath of
// pszPath. If found, pszPath specifies a file or folder
// that cannot be pinned.
//
// Returns:
// S_OK - Pinning is allowed.
// S_FALSE - Pinning is NOT allowed.
// NOPIN_E_BADPATH - Path is not a valid UNC.
//
HRESULT CNoPinList::IsPinAllowed( LPCTSTR pszPath ) { TraceEnter(TRACE_UTIL, "CNoPinList::IsPinAllowed"); TraceAssert(NULL != pszPath); TraceAssert(::PathIsUNC(pszPath));
HRESULT hr = _Initialize(); if (SUCCEEDED(hr)) { hr = S_OK; //
// A quick optimization is to see if the tree is empty.
// If it is, any file/folder may be pinned. This helps
// performance when no pinning restriction is in place.
//
TraceAssert(NULL != m_pRoot); if (m_pRoot->HasChildren()) { if (::PathIsUNC(pszPath)) { //
// SubPathExists modifies the path. Need to make a local copy.
//
TCHAR szPath[MAX_PATH]; hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath); if (SUCCEEDED(hr)) { hr = m_pRoot->SubPathExists(szPath); if (S_FALSE == hr) { //
// Absence from the tree means pinning is allowed.
//
hr = S_OK; } else if (S_OK == hr) { //
// Presence in the tree means pinning is not allowed.
//
Trace((TEXT("Policy disallows pinning \"%s\""), pszPath)); hr = S_FALSE; } } else if (STRSAFE_E_INSUFFICIENT_BUFFER == hr) { hr = NOPIN_E_BADPATH; } } else { hr = NOPIN_E_BADPATH; } } } TraceAssert(S_OK == hr || S_FALSE == hr || NOPIN_E_BADPATH == hr);
TraceLeaveResult(hr); }
//
// Quick check to see if ANY pin might be disallowed.
// Returns:
// S_OK - Tree has content.
// S_FALSE - Tree is empty.
//
HRESULT CNoPinList::IsAnyPinDisallowed( void ) { HRESULT hr = _Initialize(); if (SUCCEEDED(hr)) { TraceAssert(NULL != m_pRoot); hr = m_pRoot->HasChildren() ? S_OK : S_FALSE; } return hr; }
//
// Initializes the no-pin list by reading path strings from the
// registry. The paths are stored in both HKLM and HKCU under
// the following key:
//
// Software\Policies\Microsoft\Windows\NetCache\NoMakeAvailableOfflineList
//
// Path strings may contain environment variables.
// Upon return the object contains a tree representing the union of all
// files and folders listed in both registry keys.
//
// Errors in reading the registry result only in paths not being added
// to the tree. No error is returned as a result of registry errors.
// If an invalid UNC path is found in the registry, an even log entry
// is recorded.
//
// Returns:
// S_OK - List successfully loaded.
// S_FALSE - List already initialized.
// E_OUTOFMEMORY - Insufficient memory.
// Other errors are possible.
//
HRESULT CNoPinList::_Initialize( void ) { TraceEnter(TRACE_UTIL, "CNoPinList::_Initialize"); HRESULT hr = S_OK;
if (NULL != m_pRoot) { //
// List is already initialized.
//
hr = S_FALSE; } else { m_pRoot = new CNode; if (NULL == m_pRoot) { hr = E_OUTOFMEMORY; } else { const HKEY rghkeyRoot[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
TCHAR szKey[MAX_PATH]; PathCombine(szKey, REGSTR_KEY_OFFLINEFILESPOLICY, REGSTR_SUBKEY_NOMAKEAVAILABLEOFFLINELIST);
for (int i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(rghkeyRoot); i++) { HKEY hkey; LONG lResult = ::RegOpenKeyEx(rghkeyRoot[i], szKey, 0, KEY_QUERY_VALUE, &hkey);
if (ERROR_SUCCESS == lResult) { TCHAR szName[MAX_PATH]; DWORD dwIndex = 0; DWORD cchName = ARRAYSIZE(szName); //
// Enumerate the paths listed in the registry.
//
while (SUCCEEDED(hr) && ERROR_SUCCESS == ::RegEnumValue(hkey, dwIndex, szName, &cchName, NULL, NULL, NULL, NULL)) { //
// Install the path string from the registry into the
// tree. This function will expand any embedded environment strings
// as well as convert mapped drive specs to remote UNC paths.
//
hr = _InitPathFromRegistry(szName); if (NOPIN_E_BADPATH == hr) { //
// This is a special error. It means someone has
// put bad data into the registry. "Bad" meaning
// that the path is not or does not expand to a valid UNC
// path string.
// Write an event log entry to tell the admin. The
// entry is generated at event logging level 1. I don't want
// it filling up an event log under normal conditions but
// I want an admin to figure it out in case their no-pin
// policy appears to be not working.
//
// The MSG template is this (english):
//
// "The registry value '%1' in key '%2\%3' is not, or does not
// expand to, a valid UNC path."
//
// We handle the error here because this is where we still have the
// value read from the registry. We include that in the event
// log entry so the admin can easily find it.
//
CscuiEventLog log; log.Push(szName);
if (HKEY_LOCAL_MACHINE == rghkeyRoot[i]) { log.Push(TEXT("HKEY_LOCAL_MACHINE")); } else { log.Push(TEXT("HKEY_CURRENT_USER")); } log.Push(szKey); log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_INVALID_UNCPATH_INREG, 1); //
// We do not abort processing because of a bad reg value.
//
hr = S_OK; }
cchName = ARRAYSIZE(szName); dwIndex++; }
::RegCloseKey(hkey); hkey = NULL; } } } } TraceLeaveResult(hr); }
//
// Given a path string read from the registry, this function expands
// any embedded environment strings, converts any mapped drive letters
// to their corresponding remote UNC paths and installs the resulting
// path string into the tree.
//
HRESULT CNoPinList::_InitPathFromRegistry( LPCTSTR pszPath ) { TraceEnter(TRACE_UTIL, "CNoPinList::_InitPathFromRegistry"); TraceAssert(NULL != pszPath);
HRESULT hr = S_OK;
TCHAR szNameExp[MAX_PATH]; // Expanded name string buffer.
//
// Expand any embedded environment strings.
//
if (0 == ::ExpandEnvironmentStrings(pszPath, szNameExp, ARRAYSIZE(szNameExp))) { const DWORD dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); Trace((TEXT("Error %d expanding \"%s\""), dwErr, pszPath)); } if (SUCCEEDED(hr)) { LPCTSTR pszUncPath = NULL; LPTSTR pszRemotePath = NULL; // Created by GetRemotePath if necessary.
//
// Convert a common typing mistake.
// Remember, these are reg entries. They could contain most anything.
//
for (LPTSTR s = szNameExp; *s; s++) { if (TEXT('/') == *s) { *s = TEXT('\\'); } }
if (::PathIsUNC(szNameExp)) { //
// Path is a UNC path. We're golden.
//
pszUncPath = szNameExp; } else { //
// Path is probably a mapped drive.
// Get its remote UNC path. This API returns S_FALSE
// if the remote drive is not connected or if it's a local drive.
//
hr = ::GetRemotePath(szNameExp, &pszRemotePath); if (SUCCEEDED(hr)) { if (S_OK == hr) { pszUncPath = pszRemotePath; } else if (S_FALSE == hr) { //
// Path was either to a local drive or to a net drive that
// isn't connected. Either way it's an invalid drive that
// won't be considered in the no-pin logic. Use the expanded
// value from the registry and pass that through to AddPath()
// where it will be rejected as an invalid UNC path.
//
TraceAssert(NULL == pszRemotePath); pszUncPath = szNameExp; hr = S_OK; } } } if (SUCCEEDED(hr)) { TraceAssert(NULL != pszUncPath); TraceAssert(pszUncPath == szNameExp || pszUncPath == pszRemotePath); //
// Insert the UNC path into the tree.
// At this point, a path may or may not be UNC. _AddPath()
// will verify this.
//
hr = _AddPath(pszUncPath); } if (NULL != pszRemotePath) { ::LocalFree(pszRemotePath); } } TraceLeaveResult(hr); }
//
// Adds a path to the tree. If this is a sub-path of an existing
// path in the tree, the remainder of the existing path is removed
// from the tree.
//
// Returns:
// S_OK - Path successfully added.
// E_OUTOFMEMORY - Insufficient memory.
// NOPIN_E_BADPATH - Invalid path string. Not a UNC.
//
HRESULT CNoPinList::_AddPath( LPCTSTR pszPath ) { TraceAssert(NULL != pszPath);
HRESULT hr = NOPIN_E_BADPATH;
if (::PathIsUNC(pszPath)) { //
// AddPath modifies the path. Need to make a local copy.
//
TraceAssert(NULL != m_pRoot); TCHAR szPath[MAX_PATH]; hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath); if (SUCCEEDED(hr)) { hr = m_pRoot->AddPath(szPath); } else if (STRSAFE_E_INSUFFICIENT_BUFFER == hr) { hr = NOPIN_E_BADPATH; } }
TraceAssert(S_OK == hr || E_OUTOFMEMORY == hr || NOPIN_E_BADPATH == hr); return hr; }
//--------------------------------------------------------------------------
// class CNoPinList::CNode
//--------------------------------------------------------------------------
CNoPinList::CNode::~CNode( void ) { if (NULL != m_pszName) { ::LocalFree(m_pszName); } delete m_pChildren; delete m_pNext; }
//
// Initializes a node's name value.
//
HRESULT CNoPinList::CNode::Initialize( LPCTSTR pszName ) { TraceAssert(NULL != pszName); TraceAssert(NULL == m_pszName);
HRESULT hr = E_OUTOFMEMORY;
if (LocalAllocString(&m_pszName, pszName)) { hr = S_OK; } return hr; }
//
// Add a child, keeping the children in alphabetical
// order by name. We trade a little time during creation
// for the speed benefits during lookup.
//
void CNoPinList::CNode::_AddChild( CNode *pChild ) { TraceAssert(NULL != pChild);
CNode **ppNode = &m_pChildren; while(NULL != *ppNode) { CNode *pNode = *ppNode; //
// Find the alphabetical insertion point.
//
TraceAssert(NULL != pNode->m_pszName); TraceAssert(NULL != pChild->m_pszName);
int diff = ::lstrcmpi(pChild->m_pszName, pNode->m_pszName); if (0 == diff) { //
// Child already exists. Don't allow duplicates.
//
return; } if (diff < 0) { //
// The new child is alphabetically "greater" than the currently
// visited node.
// Exit the loop with ppNode pointing to the pointer variable
// where we'll put the address of pChild.
//
break; } else { //
// Advance to the next node in the list.
//
ppNode = &pNode->m_pNext; } } //
// Insert the child.
//
pChild->m_pNext = *ppNode; *ppNode = pChild; }
//
// Locates a child node in a node's list of children.
// Comparison is by node name.
// Returns the address of the node if found. NULL otherwise.
//
CNoPinList::CNode * CNoPinList::CNode::_FindChild( LPCTSTR pszName ) const { TraceAssert(NULL != pszName);
CNode *pChild = NULL; for (CNode *pNode = m_pChildren; pNode; pNode = pNode->m_pNext) { //
// The list is sorted alphabetically.
//
int diff = ::lstrcmpi(pszName, pNode->m_pszName); if (diff <= 0) { //
// Either we found a match or we've passed all possible
// matches.
//
if (0 == diff) { //
// Exact match.
//
pChild = pNode; } break; } } return pChild; }
//
// Given "\\brianau1\public\bin"
// Returns address of "brianau1\public\bin", with *pcchComponent == 8.
//
// Given "public\bin"
// Returns address of "bin", with *pcchComponent == 3.
//
LPCTSTR CNoPinList::CNode::_FindNextPathComponent( // [static]
LPCTSTR pszPath, int *pcchComponent // [optional] Can be NULL.
) { TraceAssert(NULL != pszPath);
LPCTSTR pszBegin = pszPath;
const TCHAR CH_BS = TEXT('\\'); //
// Skip any leading backslashes.
//
while(*pszBegin && CH_BS == *pszBegin) ++pszBegin;
//
// Find the end of the path component.
//
LPCTSTR pszEnd = pszBegin; while(*pszEnd && CH_BS != *pszEnd) ++pszEnd;
if (NULL != pcchComponent) { *pcchComponent = int(pszEnd - pszBegin); TraceAssert(0 <= *pcchComponent); }
//
// Validate the final position of the begin and end ptrs.
//
TraceAssert(NULL != pszBegin); TraceAssert(NULL != pszEnd); TraceAssert(pszBegin >= pszPath); TraceAssert(pszBegin <= (pszPath + lstrlen(pszPath))); TraceAssert(pszEnd >= pszPath); TraceAssert(pszEnd <= (pszPath + lstrlen(pszPath))); TraceAssert(TEXT('\\') != *pszBegin); return pszBegin; }
//
// Recursively adds components of a path string to the tree.
//
HRESULT CNoPinList::CNode::AddPath( LPTSTR pszPath ) { TraceAssert(NULL != pszPath);
HRESULT hr = NOPIN_E_BADPATH; if (NULL != pszPath) { hr = S_OK;
int cchPart = 0; LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart); if (*pszPart) { TCHAR chTemp = TEXT('\0'); _SwapChars(&chTemp, pszPart + cchPart); CNode *pChild = _FindChild(pszPart);
if (NULL != pChild) { //
// Found an existing node for this part of the path.
// If the node has children, give the remainder of the path
// to this node for addition. If it doesn't that means
// it's a leaf node and all it's children are excluded from
// pinning. No reason to add any children to it.
//
_SwapChars(&chTemp, pszPart + cchPart); if (pChild->HasChildren()) { hr = pChild->AddPath(pszPart + cchPart); } } else { //
// This is a new sub-path that is not yet in the tree.
//
hr = E_OUTOFMEMORY;
pChild = new CNode(); if (NULL != pChild) { //
// Initialize the new child.
//
hr = pChild->Initialize(pszPart); _SwapChars(&chTemp, pszPart + cchPart); if (SUCCEEDED(hr)) { //
// Have the new child add the remainder of
// the path as it's children.
//
hr = pChild->AddPath(pszPart + cchPart); if (SUCCEEDED(hr)) { //
// Link the new child into the list of children.
//
_AddChild(pChild); } } if (FAILED(hr)) { delete pChild; pChild = NULL; } } } } else { //
// We're at the end of the path, that means we're at a leaf node
// and this file or directory is excluded from pinning. If it's
// a directory, all children are excluded from pinning so there's
// no reason to keep any child nodes in the tree. This keeps the
// tree trimmed to a minimum necessary size.
//
delete m_pChildren; m_pChildren = NULL; } }
TraceAssert(S_OK == hr || E_OUTOFMEMORY == hr || NOPIN_E_BADPATH == hr); return hr; }
//
// Recursively determines if a given complete subpath exists for a
// path string. If a match occurs at a given level in the tree,
// the remainder of the path string is given to the matching node
// for further searching. This process continues recursively until
// we hit a leaf node in the tree or the end of the path string,
// whichever occurs first.
//
// Returns:
// S_OK - A complete path exists that is a subpath of pszPath.
// S_FALSE - A complete path does not exist.
//
HRESULT CNoPinList::CNode::SubPathExists( LPTSTR pszPath ) const { HRESULT hr = NOPIN_E_BADPATH; if (NULL != pszPath) { hr = S_FALSE;
int cchPart = 0; LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart); if (*pszPart) { TCHAR chTemp = TEXT('\0');
_SwapChars(&chTemp, pszPart + cchPart); CNode *pChild = _FindChild(pszPart); _SwapChars(&chTemp, pszPart + cchPart);
if (NULL != pChild) { if (pChild->HasChildren()) { hr = pChild->SubPathExists(pszPart + cchPart); } else { //
// Hit a leaf node. That means that we've traversed
// down a complete subpath of the path in question.
// Pinning of this path is not allowed.
//
hr = S_OK; } } } }
TraceAssert(S_OK == hr || S_FALSE == hr || NOPIN_E_BADPATH == hr); return hr; }
#if DBG
//
// This function dumps the contents of a tree node and all it's decendents.
// The result is an indented list of nodes in the debugger output.
// Handy for debugging tree build problems.
//
void CNoPinList::_DumpNode( const CNoPinList::CNode *pNode, int iIndent ) { CNodeInspector ni(pNode); TCHAR szText[1024] = {0};
iIndent = min(iIndent, 60);
LPTSTR pszWrite = szText; UINT cchWrite = ARRAYSIZE(szText); for (int i = 0; i < iIndent; i++) { *pszWrite++ = TEXT(' '); cchWrite--; }
::OutputDebugString(TEXT("\n\r")); ::wnsprintf(pszWrite, cchWrite, TEXT("Node Address.: 0x%08X\n\r"), pNode); ::OutputDebugString(szText); ::wnsprintf(pszWrite, cchWrite, TEXT("Name.........: %s\n\r"), ni.NodeName() ? ni.NodeName() : TEXT("<null>")); ::OutputDebugString(szText); ::wnsprintf(pszWrite, cchWrite, TEXT("Children.....: 0x%08X\n\r"), ni.ChildList()); ::OutputDebugString(szText); ::wnsprintf(pszWrite, cchWrite, TEXT("Next Sibling.: 0x%08X\n\r"), ni.NextSibling()); ::OutputDebugString(szText);
if (NULL != ni.ChildList()) { _DumpNode(ni.ChildList(), iIndent + 5); } if (NULL != ni.NextSibling()) { _DumpNode(ni.NextSibling(), iIndent); } }
//
// Dump the entire tree starting with the root.
//
void CNoPinList::Dump( void ) { ::OutputDebugString(TEXT("\n\rDumping CNoPinList\n\r")); if (NULL != m_pRoot) { _DumpNode(m_pRoot, 0); } } #endif // DBG
|