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.
1714 lines
50 KiB
1714 lines
50 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1996 - 1999
|
|
//
|
|
// File: rshx32.cpp
|
|
//
|
|
// Remote administration shell extension.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Include files //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "rshx32.h"
|
|
#include <winnetwk.h> // WNetGetConnection
|
|
#include <lm.h>
|
|
#include <lmdfs.h> // NetDfsGetClientInfo
|
|
#include <atlconv.h>
|
|
|
|
#include <initguid.h>
|
|
DEFINE_GUID(CLSID_NTFSSecurityExt, 0x1f2e5c40, 0x9550, 0x11ce, 0x99, 0xd2, 0x00, 0xaa, 0x00, 0x6e, 0x08, 0x6c);
|
|
DEFINE_GUID(CLSID_PrintSecurityExt, 0xf37c5810, 0x4d3f, 0x11d0, 0xb4, 0xbf, 0x00, 0xaa, 0x00, 0xbb, 0xb7, 0x23);
|
|
|
|
#define IID_PPV_ARG(IType, ppType) IID_##IType, reinterpret_cast<void**>(static_cast<IType**>(ppType))
|
|
|
|
#define RSX_SECURITY_CHECKED 0x00000001L
|
|
#define RSX_HAVE_SECURITY 0x00000002L
|
|
|
|
#define DOBJ_RES_CONT 0x00000001L
|
|
#define DOBJ_RES_ROOT 0x00000002L
|
|
#define DOBJ_VOL_NTACLS 0x00000004L // NTFS or OFS
|
|
|
|
|
|
class CRShellExtCF : public IClassFactory
|
|
{
|
|
protected:
|
|
ULONG m_cRef;
|
|
SE_OBJECT_TYPE m_seType;
|
|
|
|
public:
|
|
CRShellExtCF(SE_OBJECT_TYPE seType);
|
|
~CRShellExtCF();
|
|
|
|
// IUnknown methods
|
|
STDMETHODIMP QueryInterface(REFIID, void **);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
// IClassFactory methods
|
|
STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, void **);
|
|
STDMETHODIMP LockServer(BOOL);
|
|
};
|
|
|
|
class CRShellExt : public IShellExtInit, IShellPropSheetExt, IContextMenu
|
|
{
|
|
protected:
|
|
ULONG m_cRef;
|
|
SE_OBJECT_TYPE m_seType;
|
|
IDataObject *m_lpdobj; // interface passed in by shell
|
|
HRESULT m_hrSecurityCheck;
|
|
DWORD m_dwSIFlags;
|
|
LPTSTR m_pszServer;
|
|
LPTSTR m_pszObject;
|
|
HDPA m_hItemList;
|
|
BOOL m_bShowLossInheritedAclWarning;
|
|
|
|
public:
|
|
CRShellExt(SE_OBJECT_TYPE seType);
|
|
~CRShellExt();
|
|
|
|
// IUnknown methods
|
|
STDMETHODIMP QueryInterface(REFIID, void **);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
// IShellExtInit method
|
|
STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
|
|
|
|
// IShellPropSheetExt methods
|
|
STDMETHODIMP AddPages(LPFNADDPROPSHEETPAGE, LPARAM);
|
|
STDMETHODIMP ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM);
|
|
|
|
//IContextMenu methods
|
|
STDMETHODIMP QueryContextMenu(HMENU hMenu,
|
|
UINT indexMenu,
|
|
UINT idCmdFirst,
|
|
UINT idCmdLast,
|
|
UINT uFlags);
|
|
|
|
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi);
|
|
|
|
STDMETHODIMP GetCommandString(UINT_PTR idCmd,
|
|
UINT uFlags,
|
|
UINT *reserved,
|
|
LPSTR pszName,
|
|
UINT cchMax);
|
|
private:
|
|
STDMETHODIMP DoSecurityCheck(LPIDA pIDA);
|
|
STDMETHODIMP CheckForSecurity(LPIDA pIDA);
|
|
STDMETHODIMP CreateSI(LPSECURITYINFO *ppsi);
|
|
STDMETHODIMP AddSecurityPage(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam);
|
|
|
|
BOOL IsAddPrinterWizard() const;
|
|
|
|
STDMETHODIMP AddMountedVolumePage(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam);
|
|
|
|
};
|
|
typedef CRShellExt* PRSHELLEXT;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Global variables //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HINSTANCE g_hInstance = NULL;
|
|
LONG g_cRefThisDll = 0;
|
|
CLIPFORMAT g_cfShellIDList = 0;
|
|
CLIPFORMAT g_cfPrinterGroup = 0;
|
|
CLIPFORMAT g_cfMountedVolume = 0;
|
|
HMODULE g_hAclui = NULL;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Private prototypes //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void GetFileInfo(LPCTSTR pszPath,
|
|
LPDWORD pdwFileType,
|
|
LPTSTR pszServer,
|
|
ULONG cchServer,
|
|
BOOL *pbShowLossInheritedAclWarning = NULL);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// General routines //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Dll's entry point.
|
|
|
|
In order to service requests for file selection information from
|
|
any of the file manager extensions to be included in this library,
|
|
we must first register a window class to accept these requests.
|
|
|
|
The Microsoft_Network provider transfers information via a private
|
|
clipboard format called "Net Resource" which we must register.
|
|
|
|
Arguments:
|
|
|
|
Same as DllEntryPoint.
|
|
|
|
Return Values:
|
|
|
|
Same as DllEntryPoint.
|
|
|
|
--*/
|
|
|
|
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void * /*lpReserved*/)
|
|
{
|
|
switch (dwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
g_hInstance = hInstance;
|
|
g_cfShellIDList = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLIDLIST);
|
|
g_cfPrinterGroup = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_PRINTERGROUP);
|
|
g_cfMountedVolume = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_MOUNTEDVOLUME);
|
|
DebugProcessAttach();
|
|
TraceSetMaskFromCLSID(CLSID_NTFSSecurityExt);
|
|
#ifndef DEBUG
|
|
DisableThreadLibraryCalls(hInstance);
|
|
#endif
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
if (g_hAclui)
|
|
FreeLibrary(g_hAclui);
|
|
DebugProcessDetach();
|
|
break;
|
|
|
|
case DLL_THREAD_DETACH:
|
|
DebugThreadDetach();
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by shell to create a class factory object.
|
|
|
|
Arguments:
|
|
|
|
rclsid - reference to class id specifier.
|
|
riid - reference to interface id specifier.
|
|
ppv - pointer to location to receive interface pointer.
|
|
|
|
Return Values:
|
|
|
|
Returns HRESULT signifying success or failure.
|
|
|
|
--*/
|
|
|
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
|
|
{
|
|
HRESULT hr;
|
|
SE_OBJECT_TYPE seType;
|
|
|
|
*ppv = NULL;
|
|
|
|
if (IsEqualCLSID(rclsid, CLSID_NTFSSecurityExt))
|
|
seType = SE_FILE_OBJECT;
|
|
else if (IsEqualCLSID(rclsid, CLSID_PrintSecurityExt))
|
|
seType = SE_PRINTER;
|
|
else
|
|
return CLASS_E_CLASSNOTAVAILABLE;
|
|
|
|
CRShellExtCF *pShellExtCF = new CRShellExtCF(seType); // ref == 1
|
|
|
|
if (!pShellExtCF)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = pShellExtCF->QueryInterface(riid, ppv);
|
|
|
|
pShellExtCF->Release(); // release initial ref
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by shell to find out if dll can be unloaded.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Values:
|
|
|
|
Returns S_OK if dll can be unloaded, S_FALSE if not.
|
|
|
|
--*/
|
|
|
|
STDAPI DllCanUnloadNow()
|
|
{
|
|
return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
|
|
}
|
|
|
|
|
|
STDAPI DllRegisterServer(void)
|
|
{
|
|
return CallRegInstall(g_hInstance, "DefaultInstall");
|
|
}
|
|
|
|
|
|
STDAPI DllUnregisterServer(void)
|
|
{
|
|
return CallRegInstall(g_hInstance, "DefaultUninstall");
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Class factory object implementation //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
CRShellExtCF::CRShellExtCF(SE_OBJECT_TYPE seType) : m_cRef(1), m_seType(seType)
|
|
{
|
|
InterlockedIncrement(&g_cRefThisDll);
|
|
}
|
|
|
|
CRShellExtCF::~CRShellExtCF()
|
|
{
|
|
ASSERT( 0 != g_cRefThisDll );
|
|
InterlockedDecrement(&g_cRefThisDll);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Class factory object implementation (IUnknown) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CRShellExtCF::AddRef()
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CRShellExtCF::Release()
|
|
{
|
|
if (--m_cRef == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP CRShellExtCF::QueryInterface(REFIID riid, void ** ppv)
|
|
{
|
|
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
|
|
{
|
|
*ppv = (IClassFactory *)this;
|
|
m_cRef++;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Class factory object implementation (IClassFactory) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Support for IClassFactory::CreateInstance.
|
|
|
|
Arguments:
|
|
|
|
pUnkOuter - pointer to controlling unknown.
|
|
riid - reference to interface id specifier.
|
|
ppvObj - pointer to location to receive interface pointer.
|
|
|
|
Return Values:
|
|
|
|
Returns HRESULT signifying success or failure.
|
|
|
|
--*/
|
|
|
|
STDMETHODIMP CRShellExtCF::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void ** ppvObj)
|
|
{
|
|
*ppvObj = NULL;
|
|
|
|
if (pUnkOuter)
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
CRShellExt *pShellExt = new CRShellExt(m_seType);// ref count == 1
|
|
|
|
if (!pShellExt)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hr = pShellExt->QueryInterface(riid, ppvObj);
|
|
pShellExt->Release(); // release initial ref
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Support for IClassFactory::LockServer (not implemented).
|
|
|
|
Arguments:
|
|
|
|
fLock - true if lock count to be incremented.
|
|
|
|
Return Values:
|
|
|
|
Returns E_NOTIMPL.
|
|
|
|
--*/
|
|
|
|
STDMETHODIMP CRShellExtCF::LockServer(BOOL /*fLock*/)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
CRShellExt::CRShellExt(SE_OBJECT_TYPE seType) : m_cRef(1), m_seType(seType),
|
|
m_dwSIFlags(SI_EDIT_ALL | SI_ADVANCED | SI_EDIT_EFFECTIVE), m_hrSecurityCheck((HRESULT)-1),
|
|
m_hItemList(NULL),
|
|
m_bShowLossInheritedAclWarning(FALSE)
|
|
{
|
|
InterlockedIncrement(&g_cRefThisDll);
|
|
}
|
|
|
|
CRShellExt::~CRShellExt()
|
|
{
|
|
DoRelease(m_lpdobj);
|
|
|
|
LocalFreeString(&m_pszServer);
|
|
LocalFreeString(&m_pszObject);
|
|
|
|
LocalFreeDPA(m_hItemList);
|
|
|
|
ASSERT( 0 != g_cRefThisDll );
|
|
InterlockedDecrement(&g_cRefThisDll);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation (IUnknown) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
CRShellExt::AddRef()
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
CRShellExt::Release()
|
|
{
|
|
if (--m_cRef == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return m_cRef;
|
|
}
|
|
|
|
STDMETHODIMP CRShellExt::QueryInterface(REFIID riid, void ** ppv)
|
|
{
|
|
if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
|
|
{
|
|
*ppv = (LPSHELLEXTINIT)this;
|
|
m_cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, IID_IContextMenu))
|
|
{
|
|
*ppv = (LPCONTEXTMENU)this;
|
|
m_cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, IID_IShellPropSheetExt))
|
|
{
|
|
*ppv = (LPSHELLPROPSHEETEXT)this;
|
|
m_cRef++;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation (IShellExtInit) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Support for IShellExtInit::Initialize.
|
|
|
|
Arguments:
|
|
|
|
pidlFolder - pointer to id list identifying parent folder.
|
|
lpdobj - pointer to IDataObject interface for selected object(s).
|
|
hKeyProgId - registry key handle.
|
|
|
|
Return Values:
|
|
|
|
Returns HRESULT signifying success or failure.
|
|
|
|
--*/
|
|
|
|
STDMETHODIMP CRShellExt::Initialize(LPCITEMIDLIST /*pidlFolder*/, IDataObject *lpdobj, HKEY /*hKeyProgID*/)
|
|
{
|
|
DoRelease(m_lpdobj);
|
|
|
|
m_lpdobj = lpdobj; // processed in AddPages
|
|
|
|
if (m_lpdobj)
|
|
m_lpdobj->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation (IShellPropSheetExt) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Support for IShellPropSheetExt::AddPages.
|
|
|
|
Arguments:
|
|
|
|
lpfnAddPage - pointer to function called to add a page.
|
|
lParam - lParam parameter to be passed to lpfnAddPage.
|
|
|
|
Return Values:
|
|
|
|
Returns HRESULT signifying success or failure.
|
|
|
|
--*/
|
|
|
|
STDMETHODIMP
|
|
CRShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam)
|
|
{
|
|
HRESULT hr;
|
|
STGMEDIUM medium = {0};
|
|
FORMATETC fe = { g_cfShellIDList, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
LPIDA pIDA = NULL;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::AddPages");
|
|
|
|
if (IsSimpleUI())
|
|
ExitGracefully(hr, E_FAIL, "No Security page in simple mode");
|
|
|
|
//
|
|
//Check if Security Tab is hidden by privacy policy
|
|
//NTRAID#NTBUG9-223899-2001/03/06-hiteshr
|
|
//
|
|
if(IsUIHiddenByPrivacyPolicy())
|
|
ExitGracefully(hr, E_FAIL, "Security Page is hidden by Privacy Policy");
|
|
|
|
// Get the ID List data
|
|
hr = m_lpdobj->GetData(&fe, &medium);
|
|
if (FAILED(hr) && m_seType == SE_FILE_OBJECT)
|
|
TraceLeaveResult(AddMountedVolumePage(lpfnAddPage, lParam));
|
|
|
|
FailGracefully(hr, "Can't get ID List format from data object");
|
|
|
|
pIDA = (LPIDA)GlobalLock(medium.hGlobal);
|
|
TraceAssert(pIDA != NULL);
|
|
|
|
// Only support single selection for printers
|
|
if (m_seType == SE_PRINTER && pIDA->cidl != 1)
|
|
ExitGracefully(hr, E_FAIL, "Printer multiple selection not supported");
|
|
|
|
hr = DoSecurityCheck(pIDA);
|
|
|
|
if (S_OK == hr)
|
|
hr = AddSecurityPage(lpfnAddPage, lParam);
|
|
|
|
exit_gracefully:
|
|
|
|
if (pIDA)
|
|
GlobalUnlock(medium.hGlobal);
|
|
ReleaseStgMedium(&medium);
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Support for IShellPropSheetExt::ReplacePages (not supported).
|
|
|
|
Arguments:
|
|
|
|
uPageID - page to replace.
|
|
lpfnReplaceWith - pointer to function called to replace a page.
|
|
lParam - lParam parameter to be passed to lpfnReplaceWith.
|
|
|
|
Return Values:
|
|
|
|
Returns E_FAIL.
|
|
|
|
--*/
|
|
|
|
STDMETHODIMP
|
|
CRShellExt::ReplacePage(UINT /* uPageID */,
|
|
LPFNADDPROPSHEETPAGE /* lpfnReplaceWith */,
|
|
LPARAM /* lParam */)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Shell extension object implementation (IContextMenu) //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// FUNCTION: IContextMenu::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
|
|
//
|
|
// PURPOSE: Called by the shell just before the context menu is displayed.
|
|
// This is where you add your specific menu items.
|
|
//
|
|
// PARAMETERS:
|
|
// hMenu - Handle to the context menu
|
|
// indexMenu - Index of where to begin inserting menu items
|
|
// idCmdFirst - Lowest value for new menu ID's
|
|
// idCmtLast - Highest value for new menu ID's
|
|
// uFlags - Specifies the context of the menu event
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
|
|
STDMETHODIMP
|
|
CRShellExt::QueryContextMenu(HMENU hMenu,
|
|
UINT indexMenu,
|
|
UINT idCmdFirst,
|
|
UINT /*idCmdLast*/,
|
|
UINT uFlags)
|
|
{
|
|
HRESULT hr = ResultFromShort(0);
|
|
STGMEDIUM medium = {0};
|
|
FORMATETC fe = { g_cfShellIDList, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
|
|
if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY))
|
|
return hr;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::QueryContextMenu");
|
|
|
|
// Get the ID List data
|
|
hr = m_lpdobj->GetData(&fe, &medium);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPIDA pIDA = (LPIDA)GlobalLock(medium.hGlobal);
|
|
TraceAssert(pIDA != NULL);
|
|
|
|
// Only support single selection
|
|
if (pIDA->cidl == 1)
|
|
{
|
|
if (S_OK == DoSecurityCheck(pIDA))
|
|
{
|
|
TCHAR szSecurity[32];
|
|
if (LoadString(g_hInstance, IDS_SECURITY_MENU, szSecurity, ARRAYSIZE(szSecurity)))
|
|
{
|
|
MENUITEMINFO mii;
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_TYPE | MIIM_ID;
|
|
mii.fType = MFT_STRING;
|
|
mii.wID = idCmdFirst;
|
|
mii.dwTypeData = szSecurity;
|
|
mii.cch = lstrlen(szSecurity);
|
|
|
|
InsertMenuItem(hMenu, indexMenu, TRUE /*fByPosition*/, &mii);
|
|
|
|
hr = ResultFromShort(1); // Return number of items we added
|
|
}
|
|
}
|
|
}
|
|
GlobalUnlock(medium.hGlobal);
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: IContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
|
|
//
|
|
// PURPOSE: Called by the shell after the user has selected on of the
|
|
// menu items that was added in QueryContextMenu().
|
|
//
|
|
// PARAMETERS:
|
|
// lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
|
|
STDMETHODIMP
|
|
CRShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
STGMEDIUM medium;
|
|
FORMATETC fe = { g_cfShellIDList, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
|
|
// Don't support named verbs
|
|
if (HIWORD(lpcmi->lpVerb))
|
|
return E_NOTIMPL;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::InvokeCommand");
|
|
|
|
// We only have one command, so we should get zero here
|
|
TraceAssert(LOWORD(lpcmi->lpVerb) == 0);
|
|
|
|
// This must be true for us to have added the command to the menu
|
|
TraceAssert(S_OK == m_hrSecurityCheck);
|
|
|
|
//
|
|
// Call ShellExecuteEx to execute the "Properties" verb on this object, and
|
|
// tell it to select the security property page.
|
|
//
|
|
|
|
// Get the ID List data
|
|
hr = m_lpdobj->GetData(&fe, &medium);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPIDA pIDA = (LPIDA)GlobalLock(medium.hGlobal);
|
|
LPITEMIDLIST pidl;
|
|
|
|
// We only support single selection for context menus
|
|
TraceAssert(pIDA && pIDA->cidl == 1);
|
|
|
|
// Build a fully qualified ID List for this object
|
|
pidl = ILCombine((LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[0]),
|
|
(LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[1]));
|
|
|
|
if (pidl != NULL)
|
|
{
|
|
TCHAR szTitle[64];
|
|
SHELLEXECUTEINFO sei =
|
|
{
|
|
sizeof(SHELLEXECUTEINFO),
|
|
(lpcmi->fMask & (SEE_MASK_HOTKEY | SEE_MASK_ICON)) | SEE_MASK_INVOKEIDLIST,
|
|
lpcmi->hwnd,
|
|
c_szProperties, // lpVerb ("Properties")
|
|
NULL, // lpFile
|
|
szTitle, // lpParameters ("Security")
|
|
NULL, // lpDirectory,
|
|
lpcmi->nShow, // nShow
|
|
NULL, // hInstApp
|
|
(LPVOID)pidl, // lpIDList
|
|
NULL, // lpClass
|
|
NULL, // hkeyClass
|
|
lpcmi->dwHotKey, // dwHotKey
|
|
lpcmi->hIcon, // hIcon
|
|
NULL // hProcess
|
|
};
|
|
|
|
LoadString(g_hInstance, IDS_PROPPAGE_TITLE, szTitle, ARRAYSIZE(szTitle));
|
|
|
|
// Put up the properties dialog
|
|
if (!ShellExecuteEx(&sei))
|
|
{
|
|
DWORD dwErr = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
|
|
ILFree(pidl);
|
|
}
|
|
|
|
GlobalUnlock(medium.hGlobal);
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// SHObjectProperties builds a pidl to the object and then calls
|
|
// ShellExecuteEx. Similar to above, but it does more work to obtain the
|
|
// ID lists (which we already have).
|
|
//
|
|
SHObjectProperties(lpcmi->hwnd,
|
|
m_seType == SE_PRINTER ? SHOP_PRINTERNAME : SHOP_FILEPATH,
|
|
m_pszObject,
|
|
TEXT("Security"));
|
|
#endif
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: IContextMenu::GetCommandString(UINT, UINT, UINT, LPSTR, UINT)
|
|
//
|
|
// PURPOSE: Called by the shell after the user has selected on of the
|
|
// menu items that was added in QueryContextMenu().
|
|
//
|
|
// PARAMETERS:
|
|
// lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT signifying success or failure.
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
STDMETHODIMP
|
|
CRShellExt::GetCommandString(UINT_PTR /*idCmd*/,
|
|
UINT uFlags,
|
|
LPUINT /*reserved*/,
|
|
LPSTR pszName,
|
|
UINT cchMax)
|
|
{
|
|
if (uFlags == GCS_HELPTEXT)
|
|
{
|
|
LoadString(g_hInstance, IDS_SECURITY_HELPSTRING, (LPTSTR)pszName, cchMax);
|
|
return S_OK;
|
|
}
|
|
|
|
// Must be some other flag that we don't handle
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CRShellExt::DoSecurityCheck(LPIDA)
|
|
//
|
|
// PURPOSE: Helper function called by the Property Sheet and Context Menu
|
|
// extension code. Used to determine whether to add the menu item
|
|
// or property sheet.
|
|
//
|
|
// PARAMETERS:
|
|
// pIDA - pointer to ID List Array specifying selected objects
|
|
//
|
|
// RETURN VALUE: none
|
|
//
|
|
// COMMENTS:
|
|
// The results are stored in m_hrSecurityCheck, m_dwSIFlags, m_pszServer, and m_pszObject
|
|
//
|
|
STDMETHODIMP CRShellExt::DoSecurityCheck(LPIDA pIDA)
|
|
{
|
|
if (((HRESULT)-1) == m_hrSecurityCheck)
|
|
{
|
|
if (m_seType == SE_PRINTER && IsAddPrinterWizard())
|
|
m_hrSecurityCheck = HRESULT_FROM_WIN32(ERROR_NO_SECURITY_ON_OBJECT);
|
|
else
|
|
m_hrSecurityCheck = CheckForSecurity(pIDA);
|
|
}
|
|
return m_hrSecurityCheck;
|
|
}
|
|
|
|
//
|
|
// PURPOSE: Helper function called by CRShellExt::DoSecurityCheck
|
|
//
|
|
// PARAMETERS: pIDA - pointer to ID List array
|
|
//
|
|
// RETURN VALUE: HRESULT - S_OK if ACL editing can proceed
|
|
//
|
|
// COMMENTS:
|
|
// The results are stored in m_dwSIFlags, m_pszServer, and m_pszObject
|
|
//
|
|
STDMETHODIMP CRShellExt::CheckForSecurity(LPIDA pIDA)
|
|
{
|
|
HRESULT hr;
|
|
TCHAR szServer[MAX_PATH];
|
|
LPTSTR pszItem = NULL;
|
|
// LPTSTR pszAlternate = NULL;
|
|
DWORD dwFlags = 0;
|
|
UINT cItems;
|
|
IShellFolder2 * psf = NULL;
|
|
LPCITEMIDLIST pidl;
|
|
DWORD dwAttr;
|
|
DWORD dwPrivs[] = { SE_SECURITY_PRIVILEGE, SE_TAKE_OWNERSHIP_PRIVILEGE };
|
|
HANDLE hToken = INVALID_HANDLE_VALUE;
|
|
ACCESS_MASK dwAccess = 0;
|
|
UINT i;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::CheckForSecurity");
|
|
TraceAssert(m_pszServer == NULL); // Shouldn't get called twice
|
|
TraceAssert(pIDA != NULL);
|
|
|
|
szServer[0] = TEXT('\0');
|
|
|
|
cItems = pIDA->cidl;
|
|
TraceAssert(cItems >= 1);
|
|
|
|
//We don't show effective perm page for multiple selection
|
|
if (cItems > 1)
|
|
m_dwSIFlags &= ~SI_EDIT_EFFECTIVE;
|
|
|
|
IShellFolder2 *psfRoot = NULL;
|
|
LPCITEMIDLIST pidlFolder = (LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[0]);
|
|
hr = BindToObjectEx(NULL, pidlFolder, NULL, IID_PPV_ARG(IShellFolder2, &psfRoot));
|
|
FailGracefully(hr, "Unable to bind to folder");
|
|
TraceAssert(psfRoot);
|
|
|
|
|
|
// Create list for item paths
|
|
TraceAssert(NULL == m_hItemList);
|
|
m_hItemList = DPA_Create(4);
|
|
if (NULL == m_hItemList)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create DPA");
|
|
|
|
//
|
|
// Get the first item and see if it supports security
|
|
//
|
|
LPCITEMIDLIST pidlItem = (LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[1]);
|
|
hr = BindToFolderIDListParent(psfRoot, pidlItem, IID_PPV_ARG(IShellFolder2, &psf), &pidl);
|
|
FailGracefully(hr, "Unable to get item name");
|
|
|
|
hr = IDA_GetItemName(psf, pidl, &pszItem);
|
|
FailGracefully(hr, "Unable to get item name");
|
|
|
|
dwAttr = SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM;
|
|
hr = psf->GetAttributesOf(1, &pidl, &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
DoRelease(psf);
|
|
|
|
//
|
|
//If ACLUI is invoked for filesystem and object is not of filesystem
|
|
//return E_FAIL
|
|
//
|
|
if ((m_seType == SE_FILE_OBJECT) && !(dwAttr & SFGAO_FILESYSTEM))
|
|
ExitGracefully(hr, E_FAIL, "Not a filesystem object");
|
|
|
|
// in the case that an item is both folder and stream, assume its a stream (.zip, .cab file)
|
|
// and not a container
|
|
if ((dwAttr & (SFGAO_FOLDER | SFGAO_STREAM)) == SFGAO_FOLDER)
|
|
dwFlags |= DOBJ_RES_CONT;
|
|
|
|
//
|
|
// Check access on the first item only. If we can write the DACL
|
|
// on the first one, we will try (later) to write to all items
|
|
// in the selection and report any errors at that time.
|
|
//
|
|
hToken = EnablePrivileges(dwPrivs, ARRAYSIZE(dwPrivs));
|
|
|
|
switch (m_seType)
|
|
{
|
|
case SE_FILE_OBJECT:
|
|
GetFileInfo(pszItem, &dwFlags, szServer, ARRAYSIZE(szServer),&m_bShowLossInheritedAclWarning);
|
|
if (dwFlags & DOBJ_VOL_NTACLS)
|
|
hr = CheckFileAccess(pszItem, &dwAccess);
|
|
else
|
|
hr = HRESULT_FROM_WIN32(ERROR_NO_SECURITY_ON_OBJECT);
|
|
break;
|
|
|
|
case SE_PRINTER:
|
|
// Printers are containers (they contain documents)
|
|
// and they don't have a parent (for acl editing purposes)
|
|
dwFlags = DOBJ_RES_CONT | DOBJ_RES_ROOT;
|
|
hr = CheckPrinterAccess(pszItem, &dwAccess, szServer, ARRAYSIZE(szServer));
|
|
break;
|
|
|
|
default:
|
|
hr = E_UNEXPECTED;
|
|
}
|
|
FailGracefully(hr, "No access");
|
|
|
|
// If we can't do anything security related, and only one item
|
|
// was selected bail out.
|
|
// Continue in case of multiple selection. We will show the
|
|
// error with the name of problem file when security page
|
|
// is brought up.
|
|
if (!(dwAccess & ALL_SECURITY_ACCESS) && (cItems == 1))
|
|
ExitGracefully(hr, E_ACCESSDENIED, "No access");
|
|
|
|
// Remember the server name
|
|
if (TEXT('\0') != szServer[0])
|
|
{
|
|
hr = LocalAllocString(&m_pszServer, szServer);
|
|
FailGracefully(hr, "LocalAlloc failed");
|
|
}
|
|
|
|
// Remember the item path
|
|
DPA_AppendPtr(m_hItemList, pszItem);
|
|
pszItem = NULL;
|
|
|
|
if (!(dwAccess & WRITE_DAC))
|
|
m_dwSIFlags |= SI_READONLY;
|
|
|
|
if (!(dwAccess & WRITE_OWNER))
|
|
{
|
|
if (!(dwAccess & READ_CONTROL))
|
|
m_dwSIFlags &= ~SI_EDIT_OWNER;
|
|
else
|
|
m_dwSIFlags |= SI_OWNER_READONLY;
|
|
}
|
|
|
|
if (!(dwAccess & ACCESS_SYSTEM_SECURITY))
|
|
m_dwSIFlags &= ~SI_EDIT_AUDITS;
|
|
|
|
//
|
|
// Check the rest of the selection. If any part of a multiple
|
|
// selection doesn't support ACLs or the selection isn't homogenous,
|
|
// then we can't create the security page.
|
|
//
|
|
for (i = 2; i <= cItems; i++)
|
|
{
|
|
DWORD dw = 0;
|
|
|
|
// We only do multiple selections for files
|
|
TraceAssert(SE_FILE_OBJECT == m_seType);
|
|
LPCITEMIDLIST pidlItem1 = (LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[i]);
|
|
hr = BindToFolderIDListParent(psfRoot, pidlItem1, IID_PPV_ARG(IShellFolder2, &psf), &pidl);
|
|
FailGracefully(hr, "Unable to get item name");
|
|
|
|
hr = IDA_GetItemName(psf, pidl, &pszItem);
|
|
FailGracefully(hr, "Unable to get item name");
|
|
|
|
dwAttr = SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM;
|
|
hr = psf->GetAttributesOf(1, &pidl, &dwAttr);
|
|
FailGracefully(hr, "Unable to get item attributes");
|
|
|
|
DoRelease(psf);
|
|
//
|
|
//If ACLUI is invoked for filesystem and object is not of filesystem
|
|
//return E_FAIL
|
|
//
|
|
if ((m_seType == SE_FILE_OBJECT) && !(dwAttr & SFGAO_FILESYSTEM))
|
|
ExitGracefully(hr, E_FAIL, "Not a filesystem object");
|
|
|
|
if ((dwAttr & (SFGAO_FOLDER | SFGAO_STREAM)) == SFGAO_FOLDER)
|
|
dw |= DOBJ_RES_CONT;
|
|
|
|
if ((dw & DOBJ_RES_CONT) != (dwFlags & DOBJ_RES_CONT))
|
|
ExitGracefully(hr, E_FAIL, "Incompatible multiple selection");
|
|
|
|
GetFileInfo(pszItem, &dw, szServer, ARRAYSIZE(szServer));
|
|
|
|
// Compare against first item. All flags and the server name
|
|
// must match, otherwise we can't edit the ACLs.
|
|
if (dw == dwFlags &&
|
|
((NULL == m_pszServer && TEXT('\0') == szServer[0]) ||
|
|
(NULL != m_pszServer && 0 == lstrcmpi(m_pszServer, szServer))))
|
|
{
|
|
// Remember the item path
|
|
DPA_AppendPtr(m_hItemList, pszItem);
|
|
pszItem = NULL;
|
|
}
|
|
else
|
|
ExitGracefully(hr, E_FAIL, "Incompatible multiple selection");
|
|
}
|
|
|
|
//
|
|
// If everything has succeeded up to this point, save some flags
|
|
// and the server and object name strings
|
|
//
|
|
if (dwFlags & DOBJ_RES_CONT)
|
|
m_dwSIFlags |= SI_CONTAINER;
|
|
|
|
//
|
|
// For Root objects (e.g. "D:\") hide the ACL Protection checkbox,
|
|
// since these objects don't appear to have parents.
|
|
//
|
|
if (dwFlags & DOBJ_RES_ROOT)
|
|
m_dwSIFlags |= SI_NO_ACL_PROTECT;
|
|
|
|
// Get the "Normal" display name to use as the object name
|
|
hr = IDA_GetItemName(psfRoot, (LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[1]),
|
|
szServer, ARRAYSIZE(szServer), SHGDN_NORMAL);
|
|
FailGracefully(hr, "Unable to get item name");
|
|
if (cItems > 1)
|
|
{
|
|
int nLength = lstrlen(szServer);
|
|
LoadString(g_hInstance, IDS_MULTISEL_ELLIPSIS, szServer + nLength, ARRAYSIZE(szServer) - nLength);
|
|
}
|
|
hr = LocalAllocString(&m_pszObject, szServer);
|
|
|
|
exit_gracefully:
|
|
|
|
ReleasePrivileges(hToken);
|
|
|
|
DoRelease(psf);
|
|
DoRelease(psfRoot);
|
|
|
|
LocalFreeString(&pszItem);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CRShellExt::CreateSI(LPSECURITYINFO *)
|
|
//
|
|
// PURPOSE: Create a SecurityInformation object of the correct type
|
|
//
|
|
// PARAMETERS: ppsi - Location to store ISecurityInformation pointer
|
|
//
|
|
// RETURN VALUE: HRESULT signifying success or failure
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
STDMETHODIMP
|
|
CRShellExt::CreateSI(LPSECURITYINFO *ppsi)
|
|
{
|
|
HRESULT hr;
|
|
CSecurityInformation *psi;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::CreateSI");
|
|
TraceAssert(ppsi != NULL);
|
|
|
|
*ppsi = NULL;
|
|
|
|
switch (m_seType)
|
|
{
|
|
case SE_FILE_OBJECT:
|
|
psi = new CNTFSSecurity(m_seType,m_bShowLossInheritedAclWarning); // ref == 1
|
|
break;
|
|
|
|
case SE_PRINTER:
|
|
psi = new CPrintSecurity(m_seType); // ref == 1
|
|
break;
|
|
|
|
default:
|
|
TraceLeaveResult(E_UNEXPECTED);
|
|
}
|
|
|
|
if (psi == NULL)
|
|
TraceLeaveResult(E_OUTOFMEMORY);
|
|
|
|
hr = psi->Initialize(m_hItemList,
|
|
m_dwSIFlags,
|
|
m_pszServer,
|
|
m_pszObject);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppsi = psi;
|
|
|
|
// The SecurityInfo object takes responsibility for these
|
|
m_hItemList = NULL;
|
|
m_pszServer = NULL;
|
|
m_pszObject = NULL;
|
|
m_hrSecurityCheck = (HRESULT)-1;
|
|
}
|
|
else
|
|
psi->Release();
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
typedef HPROPSHEETPAGE (WINAPI *PFN_CREATESECPAGE)(LPSECURITYINFO);
|
|
|
|
HPROPSHEETPAGE _CreateSecurityPage(LPSECURITYINFO psi)
|
|
{
|
|
HPROPSHEETPAGE hPage = NULL;
|
|
const TCHAR szAclui[] = TEXT("aclui.dll");
|
|
const char szCreateSecPage[] = "CreateSecurityPage";
|
|
|
|
if (!g_hAclui)
|
|
g_hAclui = LoadLibrary(szAclui);
|
|
|
|
if (g_hAclui)
|
|
{
|
|
static PFN_CREATESECPAGE s_pfnCreateSecPage = NULL;
|
|
|
|
if (!s_pfnCreateSecPage)
|
|
s_pfnCreateSecPage = (PFN_CREATESECPAGE)GetProcAddress(g_hAclui, szCreateSecPage);
|
|
|
|
if (s_pfnCreateSecPage)
|
|
hPage = (*s_pfnCreateSecPage)(psi);
|
|
}
|
|
|
|
return hPage;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CRShellExt::AddSecurityPage(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
|
|
{
|
|
HRESULT hr;
|
|
LPSECURITYINFO psi;
|
|
|
|
hr = CreateSI(&psi); // ref == 1
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HPROPSHEETPAGE hPermPage = _CreateSecurityPage(psi);
|
|
|
|
if (hPermPage)
|
|
{
|
|
if (!lpfnAddPage(hPermPage, lParam))
|
|
DestroyPropertySheetPage(hPermPage);
|
|
}
|
|
else
|
|
{
|
|
DWORD dwErr = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
|
|
psi->Release(); // release initial ref
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// PURPOSE: Check for the Add Printer wizard
|
|
//
|
|
// PARAMETERS: none
|
|
//
|
|
// RETURN VALUE: TRUE if the selected object is the Add Printer wizard,
|
|
// FALSE otherwise
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
BOOL CRShellExt::IsAddPrinterWizard() const
|
|
{
|
|
BOOL bRetval = FALSE;
|
|
STGMEDIUM medium;
|
|
FORMATETC fe = { g_cfPrinterGroup, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
TCHAR szFile[MAX_PATH];
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::IsAddPrinterWizard");
|
|
TraceAssert(m_seType == SE_PRINTER);
|
|
|
|
//
|
|
// Fail the call if m_lpdobj is NULL.
|
|
//
|
|
if ( m_lpdobj && SUCCEEDED( m_lpdobj->GetData( &fe, &medium ) ) )
|
|
{
|
|
//
|
|
// Get the selected item name.
|
|
//
|
|
if ( DragQueryFile( (HDROP)medium.hGlobal, 0, szFile, ARRAYSIZE( szFile ) ) )
|
|
{
|
|
//
|
|
// Check if this is the magic Add Printer Wizard shell object.
|
|
// The check is not case sensitive and the string is not localized.
|
|
//
|
|
if ( 0 == lstrcmpi( szFile, TEXT("WinUtils_NewObject") ) )
|
|
{
|
|
TraceMsg("Found Add Printer wizard");
|
|
bRetval = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the storage medium.
|
|
//
|
|
ReleaseStgMedium( &medium );
|
|
}
|
|
|
|
TraceLeaveValue(bRetval);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CRShellExt::AddMountedVolumePage()
|
|
//
|
|
// PURPOSE: Create Security page for mounted volume properties
|
|
//
|
|
// PARAMETERS: lpfnAddPage - pointer to function called to add a page.
|
|
// lParam - lParam parameter to be passed to lpfnAddPage.
|
|
//
|
|
// RETURN VALUE: HRESULT signifying success or failure
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
STDMETHODIMP
|
|
CRShellExt::AddMountedVolumePage(LPFNADDPROPSHEETPAGE lpfnAddPage,
|
|
LPARAM lParam)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
STGMEDIUM medium = {0};
|
|
FORMATETC fe = { g_cfMountedVolume, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
TCHAR szMountPoint[MAX_PATH];
|
|
TCHAR szVolumeID[MAX_PATH];
|
|
TCHAR szLabel[64];
|
|
LPTSTR pszVolID = NULL;
|
|
DWORD dwVolFlags = 0;
|
|
DWORD dwPrivs[] = { SE_SECURITY_PRIVILEGE, SE_TAKE_OWNERSHIP_PRIVILEGE };
|
|
HANDLE hToken = INVALID_HANDLE_VALUE;
|
|
ACCESS_MASK dwAccess = 0;
|
|
BOOL bHasSecurity = FALSE;
|
|
|
|
TraceEnter(TRACE_RSHX32, "CRShellExt::AddMountedVolumePage");
|
|
TraceAssert(m_seType == SE_FILE_OBJECT);
|
|
TraceAssert(m_lpdobj);
|
|
|
|
// Try to get the mounted volume host folder path
|
|
hr = m_lpdobj->GetData(&fe, &medium);
|
|
FailGracefully(hr, "Not a mounted volume");
|
|
|
|
// Get the host folder path
|
|
if (!DragQueryFile((HDROP)medium.hGlobal, 0, szMountPoint, ARRAYSIZE(szMountPoint)))
|
|
ExitGracefully(hr, E_FAIL, "Can't get mount point from storage medium");
|
|
|
|
PathAddBackslash(szMountPoint);
|
|
|
|
// Get the volume ID, which looks like
|
|
// "\\?\Volume{9e2df3f5-c7f1-11d1-84d5-000000000000}\"
|
|
if (!GetVolumeNameForVolumeMountPoint(szMountPoint, szVolumeID, ARRAYSIZE(szVolumeID)))
|
|
ExitGracefully(hr, E_FAIL, "GetVolumeNameForVolumeMountPoint failed");
|
|
|
|
if (GetVolumeInformation(szMountPoint, //szVolumeID,
|
|
szLabel,
|
|
ARRAYSIZE(szLabel),
|
|
NULL,
|
|
NULL,
|
|
&dwVolFlags,
|
|
NULL,
|
|
0))
|
|
{
|
|
if (dwVolFlags & FS_PERSISTENT_ACLS)
|
|
{
|
|
bHasSecurity = TRUE;
|
|
}
|
|
}
|
|
else if (GetLastError() == ERROR_ACCESS_DENIED)
|
|
{
|
|
// If we can't get the volume information because we don't have
|
|
// access, then there must be security!
|
|
bHasSecurity = TRUE;
|
|
}
|
|
|
|
if (!bHasSecurity)
|
|
ExitGracefully(hr, E_FAIL, "Volume inaccessible or not NTFS");
|
|
|
|
hToken = EnablePrivileges(dwPrivs, ARRAYSIZE(dwPrivs));
|
|
|
|
hr = CheckFileAccess(szVolumeID, &dwAccess);
|
|
FailGracefully(hr, "Volume inaccessible");
|
|
|
|
// If we can't do anything security related, don't continue.
|
|
if (!(dwAccess & ALL_SECURITY_ACCESS))
|
|
ExitGracefully(hr, E_ACCESSDENIED, "No security access");
|
|
|
|
if (!(dwAccess & WRITE_DAC))
|
|
m_dwSIFlags |= SI_READONLY;
|
|
|
|
if (!(dwAccess & WRITE_OWNER))
|
|
{
|
|
if (!(dwAccess & READ_CONTROL))
|
|
m_dwSIFlags &= ~SI_EDIT_OWNER;
|
|
else
|
|
m_dwSIFlags |= SI_OWNER_READONLY;
|
|
}
|
|
|
|
if (!(dwAccess & ACCESS_SYSTEM_SECURITY))
|
|
m_dwSIFlags &= ~SI_EDIT_AUDITS;
|
|
|
|
m_dwSIFlags |= SI_CONTAINER | SI_NO_ACL_PROTECT;
|
|
|
|
|
|
|
|
if (!FormatStringID(&m_pszObject,
|
|
g_hInstance,
|
|
IDS_FMT_VOLUME_DISPLAY,
|
|
szLabel,
|
|
szMountPoint))
|
|
{
|
|
LocalAllocString(&m_pszObject, szLabel);
|
|
}
|
|
|
|
if (!m_pszObject)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to build volume display string");
|
|
|
|
m_hItemList = DPA_Create(1);
|
|
if (!m_hItemList)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create item list");
|
|
|
|
hr = LocalAllocString(&pszVolID, szVolumeID);
|
|
FailGracefully(hr, "Unable to copy volume ID string");
|
|
|
|
DPA_AppendPtr(m_hItemList, pszVolID);
|
|
pszVolID = NULL;
|
|
|
|
hr = AddSecurityPage(lpfnAddPage, lParam);
|
|
|
|
exit_gracefully:
|
|
|
|
ReleasePrivileges(hToken);
|
|
LocalFreeString(&pszVolID);
|
|
ReleaseStgMedium(&medium);
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// Miscellaneous helper functions //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
BOOL
|
|
IsDfsPath(LPTSTR pszPath, // in
|
|
LPTSTR pszServer, // out
|
|
UINT cchServer) // out
|
|
{
|
|
BOOL bIsDfs = FALSE;
|
|
WCHAR szPath[MAX_PATH];
|
|
PDFS_INFO_3 pDI3 = NULL;
|
|
WCHAR szServer[MAX_PATH];
|
|
|
|
USES_CONVERSION;
|
|
|
|
if (!PathIsUNC(pszPath))
|
|
return FALSE; // local machine
|
|
|
|
lstrcpynW(szPath, T2CW(pszPath), ARRAYSIZE(szPath));
|
|
|
|
// Check for DFS
|
|
for (;;)
|
|
{
|
|
DWORD dwErr;
|
|
|
|
__try
|
|
{
|
|
// This is delay-loaded by the linker, so
|
|
// must wrap with an exception handler.
|
|
dwErr = NetDfsGetClientInfo(szPath,
|
|
NULL,
|
|
NULL,
|
|
3,
|
|
(LPBYTE*)&pDI3);
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (NERR_Success == dwErr)
|
|
{
|
|
for (ULONG i = 0; i < pDI3->NumberOfStorages; i++)
|
|
{
|
|
if (DFS_STORAGE_STATE_ONLINE & pDI3->Storage[i].State)
|
|
{
|
|
bIsDfs = TRUE;
|
|
|
|
szServer[0] = L'\\';
|
|
szServer[1] = L'\\';
|
|
lstrcpynW(&szServer[2], pDI3->Storage[i].ServerName, ARRAYSIZE(szServer)-2);
|
|
|
|
// If this server is active, quit looking
|
|
if (DFS_STORAGE_STATE_ACTIVE & pDI3->Storage[i].State)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (NERR_DfsNoSuchVolume == dwErr)
|
|
{
|
|
// If we're at the root, then we can't go any farther.
|
|
if (PathIsRoot(szPath))
|
|
break;
|
|
|
|
// Remove the last path element and try again, if nothing is
|
|
//removed, break, don't go in infinite loop
|
|
if (!PathRemoveFileSpec(szPath))
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Some other error, bail
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bIsDfs)
|
|
{
|
|
lstrcpyn(pszServer, W2T(szServer), cchServer);
|
|
}
|
|
|
|
if (NULL != pDI3)
|
|
NetApiBufferFree(pDI3);
|
|
|
|
return bIsDfs;
|
|
}
|
|
|
|
|
|
void
|
|
GetVolumeInfo(LPCTSTR pszPath,
|
|
BOOL bIsFolder,
|
|
LPDWORD pdwFlags,
|
|
LPTSTR pszVolume,
|
|
ULONG cchVolume)
|
|
{
|
|
TCHAR szVolume[MAX_PATH];
|
|
TCHAR szVolumeID[MAX_PATH];
|
|
|
|
//
|
|
// The path can be DFS or contain volume mount points, so start
|
|
// with the full path and try GetVolumeInformation on successively
|
|
// shorter paths until it succeeds or we run out of path.
|
|
//
|
|
// However, if it's a volume mount point, we're interested in the
|
|
// the host folder's volume so back up one level to start. The
|
|
// child volume is handled separately (see AddMountedVolumePage).
|
|
//
|
|
|
|
lstrcpyn(szVolume, pszPath, ARRAYSIZE(szVolume));
|
|
|
|
if (!bIsFolder
|
|
|| GetVolumeNameForVolumeMountPoint(szVolume, szVolumeID, ARRAYSIZE(szVolumeID)))
|
|
{
|
|
PathRemoveFileSpec(szVolume);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
PathAddBackslash(szVolume); // GetVolumeInformation likes a trailing '\'
|
|
|
|
if (GetVolumeInformation(szVolume,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
pdwFlags,
|
|
NULL,
|
|
0))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Access denied implies that we've reached the deepest volume
|
|
// in the path; we just can't get the flags. It also implies
|
|
// security, so assume persistent acls.
|
|
if (ERROR_ACCESS_DENIED == GetLastError())
|
|
{
|
|
*pdwFlags = FS_PERSISTENT_ACLS;
|
|
break;
|
|
}
|
|
|
|
// If we're at the root, then we can't go any farther.
|
|
if (PathIsRoot(szVolume))
|
|
break;
|
|
|
|
// Remove the last path element and try again
|
|
PathRemoveBackslash(szVolume);
|
|
//if nothing is removed break instead of going in infinite loop
|
|
if (!PathRemoveFileSpec(szVolume))
|
|
break;
|
|
}
|
|
|
|
if (pszVolume)
|
|
{
|
|
PathRemoveBackslash(szVolume);
|
|
lstrcpyn(pszVolume, szVolume, cchVolume);
|
|
}
|
|
}
|
|
|
|
/*
|
|
This function checks if pszPath is a root share. pszPath is in the format
|
|
\\server\share where "share" is shared out directory on "server". Function
|
|
attempts to get the local path of "share" on "server" directory. If local
|
|
path is root, pszPath is a root share. In all other cases (including failures)
|
|
its not. Only members of the Administrators or Account Operators local group or
|
|
those with Communication, Print, or Server operator group membership can
|
|
successfully execute the NetShareGetInfo function at level 2, so its likely
|
|
we will encounter failures and in those cases we will simply treat this as
|
|
non-root share.
|
|
*/
|
|
BOOL IsShareRoot(LPCTSTR pszPath)
|
|
{
|
|
if(!pszPath)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD dwReturn = FALSE;
|
|
do
|
|
{
|
|
//Check if pszPath is in format \\server\share
|
|
if(!PathIsUNCServerShare(pszPath))
|
|
break;
|
|
|
|
//pszShare will point to "share"
|
|
LPWSTR pszShare = PathFindFileName(pszPath);
|
|
|
|
if(!pszShare)
|
|
break;
|
|
|
|
WCHAR szServer[MAX_PATH];
|
|
if(FAILED(StringCchCopy(szServer,ARRAYSIZE(szServer),pszPath)))
|
|
break;;
|
|
|
|
//Remove the "share" portion, szServer will contain \\server
|
|
if(!PathRemoveFileSpec(szServer))
|
|
break;
|
|
|
|
//Get the local path of the share on the server
|
|
SHARE_INFO_2 *pbuf = NULL;
|
|
NET_API_STATUS status = NetShareGetInfo(szServer,
|
|
pszShare,
|
|
2,
|
|
(LPBYTE *)&pbuf);
|
|
|
|
if(status != NERR_Success)
|
|
break;
|
|
|
|
if(pbuf && pbuf->shi2_path && PathIsRoot(pbuf->shi2_path))
|
|
{
|
|
dwReturn = TRUE;
|
|
}
|
|
|
|
if(pbuf)
|
|
{
|
|
NetApiBufferFree(pbuf);
|
|
pbuf = NULL;
|
|
}
|
|
}while(0);
|
|
|
|
return dwReturn;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
Setting permissions can result, in some special cases, in loss of inherited aces.
|
|
pbShowLossInheritedAclWarning is set to TRUE if that's the case and we show
|
|
a warning.
|
|
*/
|
|
void
|
|
GetFileInfo(LPCTSTR pszPath,
|
|
LPDWORD pdwFileType,
|
|
LPTSTR pszServer,
|
|
ULONG cchServer,
|
|
BOOL *pbShowLossInheritedAclWarning)
|
|
{
|
|
DWORD dwVolumeFlags = 0;
|
|
TCHAR szVolume[MAX_PATH];
|
|
LPTSTR pszUNC = NULL;
|
|
|
|
TraceEnter(TRACE_RSHX32, "GetFileInfo");
|
|
TraceAssert(NULL != pszServer);
|
|
|
|
pszServer[0] = TEXT('\0');
|
|
|
|
if(pbShowLossInheritedAclWarning)
|
|
*pbShowLossInheritedAclWarning = FALSE;
|
|
|
|
|
|
if (!PathIsUNC(pszPath) && S_OK == GetRemotePath(pszPath, &pszUNC))
|
|
pszPath = pszUNC;
|
|
|
|
//If path is in format "\\server\share", special case this case
|
|
//to determine if it's a root.
|
|
//NTRAID#NTBUG9-501402-2002/05/06-hiteshr
|
|
if(PathIsUNCServerShare(pszPath))
|
|
{
|
|
//check if "share" is root on the "\\server"
|
|
if(IsShareRoot(pszPath))
|
|
{
|
|
*pdwFileType |= DOBJ_RES_ROOT;
|
|
}
|
|
else if(pbShowLossInheritedAclWarning)
|
|
{
|
|
//It's a UNC share which is not root. Setting acl will result in loss of
|
|
//inherited aces
|
|
*pbShowLossInheritedAclWarning = TRUE;
|
|
}
|
|
}
|
|
else if (PathIsRoot(pszPath))
|
|
{
|
|
*pdwFileType |= DOBJ_RES_ROOT;
|
|
if(pbShowLossInheritedAclWarning)
|
|
{
|
|
if(GetDriveType(pszPath) == DRIVE_REMOTE)
|
|
{
|
|
//This is a remote drive and we have been unable to determine
|
|
//if its root of drive. By default we assume, it's not root of
|
|
//drive and show warning.
|
|
*pbShowLossInheritedAclWarning = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
GetVolumeInfo(pszPath,
|
|
*pdwFileType & DOBJ_RES_CONT,
|
|
&dwVolumeFlags,
|
|
szVolume,
|
|
ARRAYSIZE(szVolume));
|
|
if (dwVolumeFlags & FS_PERSISTENT_ACLS)
|
|
{
|
|
*pdwFileType |= DOBJ_VOL_NTACLS;
|
|
|
|
if (IsDfsPath(szVolume, pszServer, cchServer))
|
|
{
|
|
}
|
|
else if (PathIsUNC(szVolume))
|
|
{
|
|
LPTSTR pSlash = StrChr(&szVolume[2], TEXT('\\'));
|
|
if (pSlash)
|
|
cchServer = min(cchServer, (ULONG)(pSlash - szVolume) + 1);
|
|
lstrcpyn(pszServer, szVolume, cchServer);
|
|
}
|
|
}
|
|
|
|
LocalFreeString(&pszUNC);
|
|
|
|
TraceLeaveVoid();
|
|
}
|