//+-------------------------------------------------------------------------
//
//  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;

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;
#if (_WIN32_WINNT >= 0x0500)
    STDMETHODIMP AddMountedVolumePage(LPFNADDPROPSHEETPAGE lpfnAddPage,
                                      LPARAM               lParam);
#endif
};
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,
                 LPTSTR *ppszAlternatePath);

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// 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()
{
    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)
{
    InterlockedIncrement(&g_cRefThisDll);
}

CRShellExt::~CRShellExt()
{
    DoRelease(m_lpdobj);

    LocalFreeString(&m_pszServer);
    LocalFreeString(&m_pszObject);

    LocalFreeDPA(m_hItemList);

    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 (_WIN32_WINNT >= 0x0500)
    if (FAILED(hr) && m_seType == SE_FILE_OBJECT)
        TraceLeaveResult(AddMountedVolumePage(lpfnAddPage, lParam));
#endif
    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), NULL);
        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, don't continue.
    if (!(dwAccess & ALL_SECURITY_ACCESS))
        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 pidlItem = (LPCITEMIDLIST)ByteOffset(pIDA, pIDA->aoffset[i]);
        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");
        
        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), NULL);
        
        // 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);  // 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 ) ) )
    {
#if (_WIN32_WINNT < 0x0500)
        //
        // In NT 4.0 the printer context menus in the shell failed
        // to handle UNICODE strings properly during a call to DragQueryFile.
        // The strings returned were always ANSI.  This has been fixed
        // in build 1393 and greater.
        //
#ifdef UNICODE
        LPDROPFILES pdf = (LPDROPFILES)GlobalLock(medium.hGlobal);
        pdf->fWide = TRUE;
        GlobalUnlock(medium.hGlobal);
#endif
#endif
        //
        // 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);
}


#if (_WIN32_WINNT >= 0x0500)
//
//  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);
}
#endif  // _WIN32_WINNT >= 0x0500


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Miscellaneous helper functions                                            //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////


#if (_WIN32_WINNT < 0x0500)

#undef PathIsUNC
STDAPI_(BOOL)
PathIsUNC(LPCTSTR psz)
{
    return (psz && psz[0] == TEXT('\\') && psz[1] == TEXT('\\'));
}

static const TCHAR c_szColonSlash[] = TEXT(":\\");

#undef PathIsRoot
STDAPI_(BOOL)
PathIsRoot(LPCTSTR pPath)
{
    return (pPath && !lstrcmpi(pPath + 1, c_szColonSlash));
}

#endif // #if (_WIN32_WINNT < 0x0500)

BOOL
IsDfsPath(LPTSTR pszPath,       // in
          LPTSTR pszServer,     // out
          UINT   cchServer,     // in
          LPTSTR pszAltPath)    // out
{
    BOOL bIsDfs = FALSE;
    WCHAR szPath[MAX_PATH];
    PDFS_INFO_3 pDI3 = NULL;
    WCHAR szServer[MAX_PATH];
    WCHAR szStorage[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);
                    lstrcpynW(szStorage, szServer, ARRAYSIZE(szStorage));
                    PathAppendW(szStorage, pDI3->Storage[i].ShareName);

                    // 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);

        // Note that EntryPath has only a single leading backslash, hence +1
        LPCTSTR pszEnd = pszPath + lstrlen(pDI3->EntryPath) + 1;
        while (TEXT('\\') == *pszEnd)
            pszEnd++;

        PathCombine(pszAltPath, szStorage, pszEnd);
    }

    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);
    }
}


void
GetFileInfo(LPCTSTR pszPath,
            LPDWORD pdwFileType,
            LPTSTR  pszServer,
            ULONG   cchServer,
            LPTSTR *ppszAlternatePath)
{
    DWORD dwVolumeFlags = 0;
    TCHAR szVolume[MAX_PATH];
    LPTSTR pszUNC = NULL;

    TraceEnter(TRACE_RSHX32, "GetFileInfo");
    TraceAssert(NULL != pszServer);

    pszServer[0] = TEXT('\0');

    if (ppszAlternatePath)
        *ppszAlternatePath = NULL;

    if (!PathIsUNC(pszPath) && S_OK == GetRemotePath(pszPath, &pszUNC))
        pszPath = pszUNC;

    if (PathIsRoot(pszPath))
        *pdwFileType |= DOBJ_RES_ROOT;

    GetVolumeInfo(pszPath,
                  *pdwFileType & DOBJ_RES_CONT,
                  &dwVolumeFlags,
                  szVolume,
                  ARRAYSIZE(szVolume));
    if (dwVolumeFlags & FS_PERSISTENT_ACLS)
    {
        TCHAR szAltVolume[MAX_PATH];

        *pdwFileType |= DOBJ_VOL_NTACLS;

        if (IsDfsPath(szVolume, pszServer, cchServer, szAltVolume))
        {
            if (ppszAlternatePath)
            {
                LPCTSTR pszEnd = pszPath + lstrlen(szVolume);
                while (TEXT('\\') == *pszEnd)
                    pszEnd++;

                *ppszAlternatePath = (LPTSTR)LocalAlloc(LPTR, StringByteSize(szAltVolume)
                                                            + StringByteSize(pszEnd));
                if (*ppszAlternatePath)
                {
                    // PathCombine asserts that the buffer is at least MAX_PATH,
                    // so don't use it here.  We know the buffer is big enough.
                    lstrcpy(*ppszAlternatePath, szAltVolume);
                    LPTSTR pszT = PathAddBackslash(*ppszAlternatePath);
                    if (pszT)
                        lstrcpy(pszT, pszEnd);
                    //PathCombine(*ppszAlternatePath, szAltVolume, pszEnd);
                }
            }
        }
        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();
}