//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1996 - 1999 // // File: rshx32.cpp // // Remote administration shell extension. // //-------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////////////// // // // Include files // // // /////////////////////////////////////////////////////////////////////////////// #include "rshx32.h" #include // WNetGetConnection #include #include // NetDfsGetClientInfo #include #include 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(static_cast(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(); }