mirror of https://github.com/tongzx/nt5src
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.
2390 lines
83 KiB
2390 lines
83 KiB
/*++
|
|
|
|
|
|
|
|
|
|
Microsoft Windows
|
|
Copyright (C) Microsoft Corporation, 1981 - 1998
|
|
|
|
Module Name:
|
|
|
|
redir.cxx
|
|
|
|
Abstract:
|
|
This module contains the implementation for the members of the class
|
|
CRedirInfo which is used to consolidate redirection information from all
|
|
the policies applied to a particular GPO and then finally do the redirection
|
|
for the policy with the highest precedence.
|
|
|
|
Author:
|
|
|
|
Rahul Thombre (RahulTh) 8/11/1998
|
|
|
|
Revision History:
|
|
|
|
8/11/1998 RahulTh Created this module.
|
|
|
|
--*/
|
|
|
|
#include "fdeploy.hxx"
|
|
|
|
//initialize some global variables
|
|
WCHAR * g_szRelativePathNames[] =
|
|
{
|
|
L"Desktop",
|
|
L"My Documents",
|
|
L"My Documents\\My Pictures",
|
|
L"Start Menu",
|
|
L"Start Menu\\Programs",
|
|
L"Start Menu\\Programs\\Startup",
|
|
L"Application Data"
|
|
};
|
|
|
|
WCHAR * g_szDisplayNames[] =
|
|
{
|
|
L"Desktop",
|
|
L"My Documents",
|
|
L"My Pictures",
|
|
L"Start Menu",
|
|
L"Programs",
|
|
L"Startup",
|
|
L"Application Data"
|
|
};
|
|
//above: use the same order and elements as in REDIRECTABLE
|
|
|
|
//global variables.
|
|
const int g_lRedirInfoSize = (int) EndRedirectable;
|
|
//static members of the class
|
|
int CRedirectInfo::m_idConstructor = 0;
|
|
|
|
CRedirectInfo gPolicyResultant [g_lRedirInfoSize];
|
|
CRedirectInfo gDeletedPolicyResultant [g_lRedirInfoSize];
|
|
CRedirectInfo gAddedPolicyResultant [g_lRedirInfoSize];
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::CRedirectInfo
|
|
//
|
|
// Synopsis: Constructor for the class
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
// History: 8/11/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
CRedirectInfo::CRedirectInfo ()
|
|
{
|
|
m_rID = (REDIRECTABLE)(m_idConstructor); //assign IDs sequentially.
|
|
m_idConstructor = (m_idConstructor + 1) % ((int)EndRedirectable);
|
|
|
|
m_pSid = NULL;
|
|
m_szLocation = NULL;
|
|
m_cbLocSize = 0;
|
|
m_szGroupRedirectionData = NULL;
|
|
|
|
ResetMembers ();
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::~CRedirectInfo
|
|
//
|
|
// Synopsis: destructor
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
// History: 10/6/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
CRedirectInfo::~CRedirectInfo ()
|
|
{
|
|
FreeAllocatedMem ();
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::FreeAllocatedMem
|
|
//
|
|
// Synopsis: frees memory allocated for member vars.
|
|
//
|
|
// Arguments: none.
|
|
//
|
|
// Returns: nothing.
|
|
//
|
|
// History: 12/17/2000 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void CRedirectInfo::FreeAllocatedMem (void)
|
|
{
|
|
if (m_szLocation)
|
|
{
|
|
delete [] m_szLocation;
|
|
m_szLocation = NULL;
|
|
m_cbLocSize = 0;
|
|
}
|
|
|
|
if (m_pSid)
|
|
{
|
|
delete [] ((BYTE *)m_pSid);
|
|
m_pSid = NULL;
|
|
}
|
|
|
|
if (m_szGroupRedirectionData)
|
|
{
|
|
delete [] m_szGroupRedirectionData;
|
|
m_szGroupRedirectionData = NULL;
|
|
}
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::ResetMembers
|
|
//
|
|
// Synopsis: resets the members of the class to their default values.
|
|
//
|
|
// Arguments: none.
|
|
//
|
|
// Returns: nothing.
|
|
//
|
|
// History: 12/17/2000 RahulTh created
|
|
//
|
|
// Notes: static members of a class, like other global variables are
|
|
// initialized only when the dll is loaded. So if this dll
|
|
// stays loaded across logons, then the fact that the globals
|
|
// have been initialized based on a previous logon can cause
|
|
// problems. Therefore, this function is used to reinitialize
|
|
// the globals.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void CRedirectInfo::ResetMembers(void)
|
|
{
|
|
FreeAllocatedMem ();
|
|
|
|
m_iRedirectingGroup = 0;
|
|
|
|
//defaults. are changed later on based on the state of this object.
|
|
m_bFollowsParent = FALSE;
|
|
m_bRedirectionAttempted = FALSE;
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
m_bValidGPO = FALSE;
|
|
m_szGPOName[0] = L'\0';
|
|
|
|
lstrcpy (m_szFolderRelativePath, g_szRelativePathNames [(int) m_rID]);
|
|
lstrcpy (m_szDisplayName, g_szDisplayNames [(int) m_rID]);
|
|
|
|
m_fDataValid = FALSE;
|
|
m_cbLocSize = 256; //start with a random amount
|
|
m_szLocation = new WCHAR [m_cbLocSize];
|
|
if (m_szLocation)
|
|
{
|
|
m_szLocation[0] = '\0';
|
|
}
|
|
else
|
|
{
|
|
m_cbLocSize = 0; //don't worry right now if memory cannot be allocated here, we will fail later
|
|
}
|
|
//set the parent and child pointers for the special parent/descendants
|
|
m_pChild = NULL; //start with defaults
|
|
m_pParent = NULL;
|
|
switch (m_rID)
|
|
{
|
|
case MyDocs:
|
|
m_pChild = this - (int) m_rID + (int) MyPics;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case MyPics:
|
|
m_pParent = this - (int) m_rID + (int) MyDocs;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case StartMenu:
|
|
m_pChild = this - (int) m_rID + (int) Programs;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case Programs:
|
|
m_pParent = this - (int) m_rID + (int) StartMenu;
|
|
m_pChild = this - (int) m_rID + (int) Startup;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case Startup:
|
|
m_pParent = this - (int) m_rID + (int) Programs;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case Desktop:
|
|
m_pChild = NULL;
|
|
m_pParent = NULL;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
case AppData:
|
|
m_pChild = NULL;
|
|
m_pParent = NULL;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
break;
|
|
}
|
|
|
|
//as a safety mechanism, load localized folder names if ghDllInstance
|
|
//has been set. note: ghDllInstance won't be set for global variables
|
|
//since their constructors before DllMain. For such variables, the
|
|
//localized names have to be called explicitly from some other function
|
|
//which is called after DllMain.
|
|
if (ghDllInstance)
|
|
LoadLocalizedNames();
|
|
|
|
//
|
|
// No need to modify m_rID or m_idConstructor. The construct has already
|
|
// taken care of it and touching them might cause undesirable results if
|
|
// not done in the proper order. Besides, these don't change across multiple
|
|
// sessions.
|
|
//
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GetFolderIndex
|
|
//
|
|
// Synopsis: a static member function that, given the name of a special
|
|
// folder, returns its id which can be used to locate the
|
|
// redirection info. for that particular folder.
|
|
//
|
|
// Arguments: [in][szFldrName : the name of the folder as stored in fdeploy.ini
|
|
//
|
|
// Returns: the id of the folder. If the folder is not redirectable, the
|
|
// function returns EndRedirectable.
|
|
//
|
|
// History: 8/11/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
REDIRECTABLE CRedirectInfo::GetFolderIndex (LPCTSTR szFldrName)
|
|
{
|
|
int i;
|
|
for (i = 0; i < (int)EndRedirectable; i++)
|
|
{
|
|
if (0 == lstrcmpi (szFldrName, g_szDisplayNames[i]))
|
|
break; //we have found a match
|
|
}
|
|
|
|
return (REDIRECTABLE)i; //if a match was not found above, i == EndRedirectable
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::LoadLocalizedNames
|
|
//
|
|
// Synopsis: loads the localized folder display names and folder relative
|
|
// paths from the resources.
|
|
//
|
|
// Arguments: none.
|
|
//
|
|
// Returns: ERROR_SUCCESS if the names were successfully loaded.
|
|
// an error code describing the cause of the failure otherwise.
|
|
//
|
|
// History: 5/6/1999 RahulTh created
|
|
//
|
|
// Notes: we cannot do this in the constructor because ghDllInstance
|
|
// is not initialized at that time and therefore LoadString will
|
|
// fail. This is because DllMain is called after the constructors.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD CRedirectInfo::LoadLocalizedNames (void)
|
|
{
|
|
UINT DisplayID;
|
|
UINT RelpathID;
|
|
|
|
switch (m_rID)
|
|
{
|
|
case MyDocs:
|
|
DisplayID = RelpathID = IDS_MYDOCS;
|
|
break;
|
|
case MyPics:
|
|
DisplayID = IDS_MYPICS;
|
|
RelpathID = IDS_MYPICS_REL;
|
|
break;
|
|
case StartMenu:
|
|
DisplayID = RelpathID = IDS_STARTMENU;
|
|
break;
|
|
case Programs:
|
|
DisplayID = IDS_PROGRAMS;
|
|
RelpathID = IDS_PROGRAMS_REL;
|
|
break;
|
|
case Startup:
|
|
DisplayID = IDS_STARTUP;
|
|
RelpathID = IDS_STARTUP_REL;
|
|
break;
|
|
case Desktop:
|
|
DisplayID = RelpathID = IDS_DESKTOP;
|
|
break;
|
|
case AppData:
|
|
DisplayID = RelpathID = IDS_APPDATA;
|
|
break;
|
|
}
|
|
|
|
//now get the localized name of the folder and the localized relative
|
|
//path names (w.r.t. the userprofile directory)
|
|
m_szLocDisplayName[0] = m_szLocFolderRelativePath[0] = '\0'; //safety
|
|
|
|
if (!LoadString (ghDllInstance, DisplayID, m_szLocDisplayName, 80))
|
|
return GetLastError();
|
|
|
|
if (DisplayID == RelpathID)
|
|
{
|
|
lstrcpy (m_szLocFolderRelativePath, m_szLocDisplayName); //top level folders
|
|
}
|
|
else
|
|
{
|
|
if (!LoadString (ghDllInstance, RelpathID, m_szLocFolderRelativePath, 80)) //special descendant folders
|
|
return GetLastError();
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GatherRedirectionInfo
|
|
//
|
|
// Synopsis: this function gathers redirection info. from the ini file
|
|
//
|
|
// Arguments: [in] pFileDB : pointer to the CFileDB object that called it
|
|
// [in] dwFlags : the flags as obtained from the ini file
|
|
// [in] bRemove : whether this is a policy that is being removed
|
|
//
|
|
// Returns: STATUS_SUCCESS if successful. An error code otherwise.
|
|
//
|
|
// History: 8/11/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD CRedirectInfo::GatherRedirectionInfo (CFileDB * pFileDB, DWORD dwFlags, BOOL bRemove)
|
|
{
|
|
DWORD Status = ERROR_SUCCESS;
|
|
DWORD len;
|
|
BOOL bStatus;
|
|
WCHAR * pwszSection = 0;
|
|
BOOL bFoundGroup;
|
|
WCHAR * pwszString = 0;
|
|
WCHAR * pwszSid = 0;
|
|
WCHAR * pwszPath = 0;
|
|
PSID Sid = NULL;
|
|
|
|
if (bRemove &&
|
|
(!gSavedSettings[m_rID].m_bValidGPO ||
|
|
(0 != _wcsicmp (pFileDB->_pwszGPOUniqueName, gSavedSettings[m_rID].m_szGPOName))
|
|
)
|
|
)
|
|
{
|
|
DebugMsg((DM_VERBOSE, IDS_IGNORE_DELETEDGPO, pFileDB->_pwszGPOName, pFileDB->_pwszGPOUniqueName, m_szLocDisplayName));
|
|
Status = ERROR_SUCCESS;
|
|
//set default values on the members just to be on the safe side.
|
|
m_fDataValid = FALSE;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
m_bValidGPO = FALSE;
|
|
m_szGPOName[0] = L'\0';
|
|
goto GatherInfoEnd;
|
|
}
|
|
|
|
//the data is valid. This function is only called when something relevant is found in the ini file.
|
|
m_fDataValid = TRUE;
|
|
|
|
//store the flags
|
|
m_dwFlags = dwFlags;
|
|
|
|
//store the GPO's unique name
|
|
if (bRemove)
|
|
{
|
|
m_bValidGPO = FALSE; //for redirection resulting from a removed GPO, we do not store the GPO name to avoid processing a removal twice
|
|
m_szGPOName[0] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
m_bValidGPO = TRUE;
|
|
wcscpy (m_szGPOName, pFileDB->_pwszGPOUniqueName);
|
|
}
|
|
|
|
//there is nothing to do if policy is not specified for this folder
|
|
if (m_dwFlags & REDIR_DONT_CARE)
|
|
goto GatherInfoSuccess;
|
|
|
|
m_bRemove = bRemove;
|
|
|
|
//also, we can do nothing right now if this is a special descendant folder
|
|
//following its parent
|
|
if (m_dwFlags & REDIR_FOLLOW_PARENT)
|
|
goto GatherInfoSuccess;
|
|
|
|
//a location has been specified by this policy
|
|
|
|
if ( ! pFileDB->GetRsopContext()->IsPlanningModeEnabled() )
|
|
{
|
|
//we must have a list of groups to which the user belongs
|
|
//each user must at least have everyone as one of his groups
|
|
if (! pFileDB->_pGroups)
|
|
goto GatherInfoErr;
|
|
}
|
|
|
|
DWORD cchSectionLen;
|
|
|
|
bStatus = pFileDB->ReadIniSection (m_szDisplayName, &pwszSection, &cchSectionLen);
|
|
|
|
if (!bStatus)
|
|
goto GatherInfoErr;
|
|
|
|
bFoundGroup = FALSE;
|
|
|
|
//
|
|
// For rsop, we need to know the list of security groups
|
|
// and redirected paths -- we'll copy this information for
|
|
// use by the rsop logging code
|
|
//
|
|
if (pFileDB->GetRsopContext()->IsRsopEnabled())
|
|
{
|
|
//
|
|
// Note that when we do the copy, the size returned
|
|
// by ReadIniSection above does not include the null terminator,
|
|
// even though thaft character is present, so we must add that character
|
|
// to the count ourselves.
|
|
//
|
|
m_szGroupRedirectionData = new WCHAR[cchSectionLen + 1];
|
|
|
|
if (m_szGroupRedirectionData)
|
|
{
|
|
RtlCopyMemory(m_szGroupRedirectionData,
|
|
pwszSection,
|
|
( cchSectionLen + 1 ) * sizeof(*pwszSection));
|
|
}
|
|
else
|
|
{
|
|
pFileDB->GetRsopContext()->DisableRsop( E_OUTOFMEMORY );
|
|
}
|
|
}
|
|
|
|
DWORD iGroup;
|
|
|
|
iGroup = 0;
|
|
|
|
for (pwszString = pwszSection;
|
|
*pwszString;
|
|
pwszString += lstrlen(pwszString) + 1)
|
|
{
|
|
pwszSid = pwszString;
|
|
pwszPath = wcschr (pwszString, L'=');
|
|
if (!pwszPath)
|
|
continue; //skip any invalid entries
|
|
|
|
//temporarily break up the sid and the path
|
|
*pwszPath++ = L'\0';
|
|
|
|
if (! *pwszPath)
|
|
{
|
|
//again an invalid path. restore the = sign and move on
|
|
pwszPath[-1] = L'='; //note: we had advanced the pointer above
|
|
continue;
|
|
}
|
|
|
|
//the entry is valid
|
|
bFoundGroup = GroupInList (pwszSid, pFileDB->_pGroups);
|
|
|
|
if (bFoundGroup)
|
|
{
|
|
m_iRedirectingGroup = iGroup;
|
|
|
|
break; //we have found a group, so break out of the loop
|
|
}
|
|
else
|
|
{
|
|
//restore the '=' sign, and try the next group
|
|
pwszPath[-1] = L'='; //note: we had advanced the pointer above
|
|
}
|
|
|
|
iGroup++;
|
|
}
|
|
|
|
if (!bFoundGroup)
|
|
{
|
|
//no group was found, so treat this as a don't care
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
}
|
|
else
|
|
{
|
|
//first store the sid
|
|
if (m_pSid)
|
|
delete [] ((BYTE*) m_pSid); //m_pSid is always allocated from our heap, so never use RtlFreeSid
|
|
|
|
m_pSid = NULL;
|
|
|
|
if (!m_bRemove)
|
|
Status = AllocateAndInitSidFromString (pwszSid, &Sid);
|
|
else
|
|
Status = AllocateAndInitSidFromString (L"S-1-1-0", &Sid); //if this is a removed policy, we set the SID to Everyone -- so that when we look at the saved settings at a later time, we shouldn't have to process the policy again (see code for NeedsProcessing)
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
goto GatherInfoEnd;
|
|
|
|
if (m_pSid)
|
|
delete [] ((BYTE*) m_pSid);
|
|
Status = MySidCopy (&m_pSid, Sid); //we want to always allocate memory for this sid from our heap
|
|
RtlFreeSid (Sid); //cleanup. must take place before the following check
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
goto GatherInfoEnd;
|
|
|
|
//we have found a group
|
|
DebugMsg ((DM_VERBOSE, IDS_GROUP_MEMBER, pwszSid, pwszPath));
|
|
SimplifyPath (pwszPath);
|
|
|
|
//
|
|
// Copy the location
|
|
// Use X:\ for paths of the form X: to avoid problems during the file
|
|
// copy phase.
|
|
//
|
|
BOOL bAppendSlash = FALSE;
|
|
len = lstrlen (pwszPath);
|
|
if (2 == len && L':' == pwszPath[1])
|
|
{
|
|
bAppendSlash = TRUE;
|
|
len++; // Ensure that the extra backslash at the end is accounted for in length calculations.
|
|
}
|
|
|
|
if (m_cbLocSize <= len)
|
|
{
|
|
//we need to reallocate memory for the location
|
|
if (m_cbLocSize)
|
|
delete [] m_szLocation;
|
|
|
|
m_cbLocSize = len + 1; // Add one for the terminating null.
|
|
m_szLocation = new TCHAR [m_cbLocSize];
|
|
if (!m_szLocation)
|
|
{
|
|
m_cbLocSize = 0;
|
|
goto GatherInfoErr;
|
|
}
|
|
}
|
|
lstrcpy (m_szLocation, pwszPath);
|
|
if (bAppendSlash)
|
|
lstrcat (m_szLocation, L"\\");
|
|
}
|
|
|
|
|
|
GatherInfoSuccess:
|
|
DebugMsg ((DM_VERBOSE, IDS_COLLECT_REDIRINFO, m_szLocDisplayName, m_dwFlags));
|
|
Status = STATUS_SUCCESS;
|
|
goto GatherInfoEnd;
|
|
|
|
GatherInfoErr:
|
|
Status = ERROR_OUTOFMEMORY;
|
|
m_fDataValid = FALSE;
|
|
m_dwFlags = REDIR_DONT_CARE; //so that these settings will be ignored while merging into global data
|
|
DebugMsg ((DM_VERBOSE, IDS_GATHER_FAILURE, Status, m_szLocDisplayName));
|
|
|
|
GatherInfoEnd:
|
|
if (pwszSection)
|
|
delete [] pwszSection;
|
|
|
|
return Status;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::WasRedirectionAttempted
|
|
//
|
|
// Synopsis: indicates whether redirection has already been attempted on
|
|
// this folder.
|
|
//
|
|
// Arguments: none.
|
|
//
|
|
// Returns: a bool indicating the required status
|
|
//
|
|
// History: 9/20/1999 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
const BOOL CRedirectInfo::WasRedirectionAttempted (void)
|
|
{
|
|
return m_bRedirectionAttempted;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GetFlags
|
|
//
|
|
// Synopsis: returns the redirection flags for the folder represented by
|
|
// this object
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: the value of the flags
|
|
//
|
|
// History: 8/12/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
const DWORD CRedirectInfo::GetFlags (void)
|
|
{
|
|
return m_dwFlags;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GetRedirStatus
|
|
//
|
|
// Synopsis: retrieves the return code of the redirection operation
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: the member m_StatusRedir
|
|
//
|
|
// History: 5/3/1999 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
const DWORD CRedirectInfo::GetRedirStatus (void)
|
|
{
|
|
return m_StatusRedir;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GetLocation
|
|
//
|
|
// Synopsis: returns the redirection location for the folder represented
|
|
// by this object
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: a pointer to the buffer containing the path
|
|
//
|
|
// History: 8/12/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
LPCTSTR CRedirectInfo::GetLocation (void)
|
|
{
|
|
return m_szLocation;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::GetLocalizedName
|
|
//
|
|
// Synopsis: returns the localized name of the folder
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: a pointer to the buffer containing the localized name
|
|
//
|
|
// History: 8/12/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
LPCTSTR CRedirectInfo::GetLocalizedName (void)
|
|
{
|
|
return m_szLocDisplayName;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::Redirect
|
|
//
|
|
// Synopsis: this function performs the actual redirection for the
|
|
// folder represented by this object.
|
|
//
|
|
// Arguments: [in] hUserToken : handle to the user token
|
|
// [in] hKeyRoot : handle to HKCU
|
|
// [in] pFileDB : pointer to the CFileDB object from which this
|
|
// function was called.
|
|
//
|
|
// Returns: a DWORD indicating the success/failure code in redirection
|
|
//
|
|
// History: 8/12/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
// It is very important to record the return code in m_StatusRedir
|
|
// before returning from this function. If we don't do that
|
|
// we might end up returning the wrong value to the policy engine
|
|
// while attempting to redirect special descendant folders which
|
|
// can affect the list of policies obtained at a subsequent logon
|
|
// session.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD CRedirectInfo::Redirect (HANDLE hUserToken,
|
|
HKEY hKeyRoot,
|
|
CFileDB* pFileDB)
|
|
{
|
|
SHARESTATUS SourceStatus = NoCSC;
|
|
SHARESTATUS DestStatus = NoCSC;
|
|
DWORD PinStatus = ERROR_SUCCESS;
|
|
WCHAR * wszProcessedPath = NULL;
|
|
|
|
if (m_bRedirectionAttempted)
|
|
{
|
|
goto Redir_CleanupAndQuit; //redirection has already been attempted
|
|
//and the success / failure code has been
|
|
//recorded in m_StatusRedir. Use the same
|
|
//error code as the return value as this
|
|
//value may not yet have been recorded in
|
|
//ProcessGroupPolicy
|
|
}
|
|
|
|
//we shall now attempt to redirect...
|
|
m_bRedirectionAttempted = TRUE;
|
|
|
|
if (m_dwFlags & REDIR_FOLLOW_PARENT)
|
|
{
|
|
//this is possible only if UpdateDescendant ran out of memory
|
|
//which means that we could not derive the folder's redirection
|
|
//info. No point trying to log an event since we are out of memory
|
|
//anyway. we just try to quit as gracefully as we can.
|
|
m_StatusRedir = ERROR_OUTOFMEMORY;
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
|
|
WCHAR wszExpandedNewPath [TARGETPATHLIMIT];
|
|
WCHAR wszExpandedCurrentPath [TARGETPATHLIMIT];
|
|
WCHAR wszHackedName [TARGETPATHLIMIT];
|
|
WCHAR wszExpandedSavedFolderPath [TARGETPATHLIMIT];
|
|
WCHAR wszExpandedNewFolderPath [TARGETPATHLIMIT];
|
|
CSavedSettings * pLocalSettings;
|
|
BOOL bCurrentPathValid; //indicates if we have been successful in obtaining the current path of the folder.
|
|
UNICODE_STRING Path;
|
|
UNICODE_STRING ExpandedPath;
|
|
|
|
//set some defaults.
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
bCurrentPathValid = TRUE;
|
|
|
|
//if the policy cares about the location of the folder, then we must
|
|
//expand the path
|
|
|
|
if (! (m_dwFlags & REDIR_DONT_CARE))
|
|
{
|
|
//show additional status messages if the verbose status is on.
|
|
DisplayStatusMessage (IDS_REDIR_CALLBACK);
|
|
|
|
if (!m_cbLocSize)
|
|
{
|
|
m_StatusRedir = ERROR_OUTOFMEMORY;
|
|
//we had run out of memory so no point trying to log an event
|
|
//we just quit as gracefully as we can.
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
|
|
if (m_bRemove)
|
|
{
|
|
wcscpy ( m_szLocation, L"%USERPROFILE%\\");
|
|
wcscat ( m_szLocation, m_szLocFolderRelativePath);
|
|
}
|
|
|
|
// Get the expanded destination path.
|
|
// First expand the homedir component, if applicable.
|
|
m_StatusRedir = ExpandHomeDirPolicyPath (m_rID,
|
|
m_szLocation,
|
|
m_bFollowsParent,
|
|
&wszProcessedPath);
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
Path.Length = (wcslen (wszProcessedPath) + 1) * sizeof (WCHAR);
|
|
Path.MaximumLength = sizeof (wszProcessedPath);
|
|
Path.Buffer = wszProcessedPath;
|
|
|
|
ExpandedPath.Length = 0;
|
|
ExpandedPath.MaximumLength = sizeof (wszExpandedNewFolderPath);
|
|
ExpandedPath.Buffer = wszExpandedNewFolderPath;
|
|
|
|
m_StatusRedir = RtlExpandEnvironmentStrings_U (
|
|
pFileDB->_pEnvBlock,
|
|
&Path,
|
|
&ExpandedPath,
|
|
NULL
|
|
);
|
|
|
|
if (STATUS_BUFFER_TOO_SMALL == m_StatusRedir)
|
|
{
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_DESTPATH_TOO_LONG,
|
|
3,
|
|
m_szLocDisplayName,
|
|
m_szLocation,
|
|
NumberToString ( TARGETPATHLIMIT )
|
|
);
|
|
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
}
|
|
|
|
if (ERROR_SUCCESS != m_StatusRedir)
|
|
{
|
|
DebugMsg ((DM_WARNING, IDS_REDIRECT_EXP_FAIL, m_szLocation, m_StatusRedir));
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_EXPAND_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString ( m_StatusRedir )
|
|
);
|
|
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
}
|
|
|
|
pLocalSettings = & (gSavedSettings[(int) m_rID]);
|
|
|
|
if (pLocalSettings->m_dwFlags & REDIR_DONT_CARE)
|
|
{
|
|
if (m_dwFlags & REDIR_DONT_CARE)
|
|
{
|
|
pLocalSettings->Save (pLocalSettings->m_szCurrentPath, m_dwFlags, NULL, NULL);
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot, FALSE);
|
|
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
goto Redir_CleanupAndQuit;
|
|
}
|
|
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT, m_szLocDisplayName, m_szLocation));
|
|
|
|
//the policy cares about the location of this doc.
|
|
|
|
//must get the correct syntax for GetLocalFilePath
|
|
lstrcpy (wszHackedName, m_szFolderRelativePath);
|
|
lstrcat (wszHackedName, L"\\");
|
|
m_StatusRedir = pFileDB->GetLocalFilePath (wszHackedName, wszExpandedCurrentPath);
|
|
|
|
//
|
|
// Expand the homedir if necessary. Note: GetLocalFilePath will not
|
|
// do it because it calls SHGetFolderPath and HOMESHARE and HOMEPATH
|
|
// are not defined at that point.
|
|
//
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
m_StatusRedir = ExpandHomeDir (m_rID,
|
|
wszExpandedCurrentPath,
|
|
TRUE,
|
|
&wszProcessedPath,
|
|
pLocalSettings->m_bHomedirChanged ? pLocalSettings->m_szLastHomedir : NULL
|
|
);
|
|
if (ERROR_SUCCESS != m_StatusRedir ||
|
|
! wszProcessedPath ||
|
|
TARGETPATHLIMIT <= lstrlen (wszProcessedPath))
|
|
{
|
|
m_StatusRedir = (ERROR_SUCCESS == m_StatusRedir) ? ERROR_BUFFER_OVERFLOW : m_StatusRedir;
|
|
}
|
|
else
|
|
{
|
|
lstrcpy (wszExpandedCurrentPath, wszProcessedPath);
|
|
}
|
|
}
|
|
|
|
//GetLocalFilePath might fail if SHGetFolderPath fails. SHGetFolderPath
|
|
//fails if the registry keys for the folder point to an invalid path and
|
|
//there is nothing in the CSC cache for it either. So in this particular
|
|
//case, we ignore the failure and treat it as a NO_MOVE. In NO_MOVE, we
|
|
//do not require the current path of the folder.
|
|
if ((ERROR_SUCCESS != m_StatusRedir) &&
|
|
ERROR_INVALID_NAME != m_StatusRedir) //the only possible error from GetLocalFilePath not generated by SHGetFolderPath
|
|
{
|
|
m_dwFlags &= ~REDIR_MOVE_CONTENTS;
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
bCurrentPathValid = FALSE;
|
|
wcscpy (wszExpandedCurrentPath, L"???"); //if bCurrentPathValid is FALSE, this string will only be used in logs
|
|
}
|
|
|
|
if (m_StatusRedir != ERROR_SUCCESS)
|
|
{
|
|
DebugMsg ((DM_WARNING, IDS_REDIRECT_NO_LOCAL, m_szLocDisplayName, m_StatusRedir));
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_EXPAND_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString ( m_StatusRedir )
|
|
);
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
|
|
//make sure that it is not already redirected
|
|
if (bCurrentPathValid && (0 == _wcsicmp (wszExpandedCurrentPath, wszExpandedNewFolderPath)))
|
|
{
|
|
pLocalSettings->Save (m_szLocation, m_dwFlags, m_pSid, m_bValidGPO ? m_szGPOName : NULL);
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot,
|
|
(m_bRemove || (m_dwFlags & REDIR_DONT_CARE))?FALSE:TRUE);
|
|
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT_INSYNC, m_szLocDisplayName,
|
|
wszExpandedNewFolderPath));
|
|
//however, it is possible that the folders have not been pinned.
|
|
//e.g. a roaming user who has already been redirected on one machine
|
|
//and now logs on to another machine for the first time.
|
|
DestStatus = GetCSCStatus (wszExpandedNewFolderPath);
|
|
if (ShareOnline == DestStatus)
|
|
{
|
|
|
|
PinStatus = PinIfNecessary (wszExpandedNewFolderPath, DestStatus);
|
|
if ( ERROR_SUCCESS != PinStatus )
|
|
DebugMsg((DM_VERBOSE, IDS_CSCPIN_FAIL,
|
|
m_szLocDisplayName, PinStatus));
|
|
|
|
CacheDesktopIni (wszExpandedNewFolderPath, DestStatus, PinFile);
|
|
}
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
goto Redir_CleanupAndQuit;
|
|
}
|
|
|
|
if (g_bCSCEnabled)
|
|
{
|
|
SourceStatus = bCurrentPathValid ? GetCSCStatus (wszExpandedCurrentPath) : PathLocal;
|
|
DestStatus = GetCSCStatus (wszExpandedNewFolderPath);
|
|
if (ShareOffline == DestStatus ||
|
|
((m_dwFlags & REDIR_MOVE_CONTENTS) && ShareOffline == SourceStatus))
|
|
{
|
|
m_StatusRedir = ERROR_CSCSHARE_OFFLINE;
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_OFFLINE,
|
|
3,
|
|
m_szLocDisplayName,
|
|
wszExpandedCurrentPath,
|
|
wszExpandedNewFolderPath
|
|
);
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
}
|
|
|
|
DebugMsg (( DM_VERBOSE, IDS_REDIRECT_PREVPATH,
|
|
pLocalSettings->m_szCurrentPath,
|
|
wszExpandedCurrentPath));
|
|
DebugMsg (( DM_VERBOSE, IDS_REDIRECT_NEWPATH, m_szLocation,
|
|
wszExpandedNewFolderPath));
|
|
|
|
m_StatusRedir = PerformRedirection (
|
|
pFileDB,
|
|
bCurrentPathValid,
|
|
wszExpandedCurrentPath,
|
|
wszExpandedNewFolderPath,
|
|
SourceStatus,
|
|
DestStatus,
|
|
hUserToken
|
|
);
|
|
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
if (m_bRemove)
|
|
{
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot, FALSE);
|
|
pLocalSettings->Save (m_szLocation, REDIR_DONT_CARE, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot,
|
|
(m_dwFlags & REDIR_DONT_CARE) ? FALSE : TRUE);
|
|
pLocalSettings->Save (m_szLocation, m_dwFlags, m_pSid, m_bValidGPO ? m_szGPOName : 0);
|
|
}
|
|
}
|
|
else if (m_pChild && m_pChild->m_bFollowsParent) //if this is a parent whose redirection was unsuccessful, do not redirect the child if it is supposed to follow
|
|
{
|
|
PreventDescendantRedirection (m_StatusRedir);
|
|
}
|
|
|
|
goto Redir_CleanupAndQuit;
|
|
}
|
|
|
|
//if we are here, it means that the saved settings don't have the
|
|
//REDIR_DONT_CARE flag set
|
|
if (m_dwFlags & REDIR_DONT_CARE)
|
|
{
|
|
//the original settings cared about the location of the folder,
|
|
//but now no one cares. So remove any redirection restrictions.
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot, FALSE);
|
|
|
|
pLocalSettings->Save (pLocalSettings->m_szCurrentPath, m_dwFlags, NULL, NULL);
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
goto Redir_CleanupAndQuit;
|
|
}
|
|
|
|
DebugMsg((DM_VERBOSE, IDS_REDIRECT, m_szLocDisplayName, m_szLocation));
|
|
|
|
// This policy cares about where this folder goes.
|
|
|
|
//
|
|
// So first expand the homedir part if applicable
|
|
// use the last value of homedir if the value of homedir has changed.
|
|
//
|
|
m_StatusRedir = ExpandHomeDir(m_rID,
|
|
pLocalSettings->m_szLastRedirectedPath,
|
|
TRUE,
|
|
&wszProcessedPath,
|
|
pLocalSettings->m_bHomedirChanged ? pLocalSettings->m_szLastHomedir : NULL);
|
|
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
// Now first expand the saved path, we will use that for redirection
|
|
Path.Length = (wcslen (wszProcessedPath) + 1) * sizeof (WCHAR);
|
|
Path.MaximumLength = Path.Length;
|
|
Path.Buffer = wszProcessedPath;
|
|
|
|
ExpandedPath.Length = 0;
|
|
ExpandedPath.MaximumLength = sizeof (wszExpandedSavedFolderPath);
|
|
ExpandedPath.Buffer = wszExpandedSavedFolderPath;
|
|
|
|
m_StatusRedir = RtlExpandEnvironmentStrings_U (
|
|
pFileDB->_pEnvBlock,
|
|
&Path,
|
|
&ExpandedPath,
|
|
NULL
|
|
);
|
|
|
|
if (STATUS_BUFFER_TOO_SMALL == m_StatusRedir)
|
|
{
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_DESTPATH_TOO_LONG,
|
|
3,
|
|
m_szLocDisplayName,
|
|
pLocalSettings->m_szLastRedirectedPath,
|
|
NumberToString ( TARGETPATHLIMIT )
|
|
);
|
|
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
}
|
|
|
|
if (ERROR_SUCCESS != m_StatusRedir)
|
|
{
|
|
DebugMsg ((DM_WARNING, IDS_REDIRECT_EXP_FAIL, pLocalSettings->m_szLastRedirectedPath, m_StatusRedir));
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_EXPAND_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString ( m_StatusRedir )
|
|
);
|
|
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
|
|
//make sure that it is not already redirected to the location.
|
|
if (0 == _wcsicmp (wszExpandedSavedFolderPath, wszExpandedNewFolderPath))
|
|
{
|
|
//the cached path and the path specified by the policy is the same
|
|
//but someone may have messed with the registry, if that is the case
|
|
//we use the path in the registry as the base path
|
|
//must get the correct syntax for GetLocalFilePath
|
|
lstrcpy (wszHackedName, m_szFolderRelativePath);
|
|
lstrcat (wszHackedName, L"\\");
|
|
m_StatusRedir = pFileDB->GetLocalFilePath (wszHackedName, wszExpandedCurrentPath);
|
|
|
|
//
|
|
// Expand the homedir if necessary. Note: GetLocalFilePath will not
|
|
// do it because it calls SHGetFolderPath and HOMESHARE and HOMEPATH
|
|
// are not defined at that point.
|
|
//
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
m_StatusRedir = ExpandHomeDir (m_rID,
|
|
wszExpandedCurrentPath,
|
|
TRUE,
|
|
&wszProcessedPath,
|
|
pLocalSettings->m_bHomedirChanged ? pLocalSettings->m_szLastHomedir : NULL
|
|
);
|
|
if (ERROR_SUCCESS != m_StatusRedir ||
|
|
! wszProcessedPath ||
|
|
TARGETPATHLIMIT <= lstrlen (wszProcessedPath))
|
|
{
|
|
m_StatusRedir = (ERROR_SUCCESS == m_StatusRedir) ? ERROR_BUFFER_OVERFLOW : m_StatusRedir;
|
|
}
|
|
else
|
|
{
|
|
lstrcpy (wszExpandedCurrentPath, wszProcessedPath);
|
|
}
|
|
}
|
|
|
|
//GetLocalFilePath may fail if SHGetFolderPath fails. This
|
|
//would mean that the registry is messed up anyway. Also, move contents
|
|
//no longer makes sense, since the path to which the reg. points to
|
|
//is invalid or worse, the registry key is missing. also, it would
|
|
//imply that there is nothing in the CSC cache
|
|
if ((ERROR_SUCCESS != m_StatusRedir) &&
|
|
ERROR_INVALID_NAME != m_StatusRedir) //the only possible error from GetLocalFilePath not generated by SHGetFolderPath
|
|
{
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
bCurrentPathValid = FALSE;
|
|
m_dwFlags &= ~REDIR_MOVE_CONTENTS;
|
|
}
|
|
|
|
if (m_StatusRedir != ERROR_SUCCESS)
|
|
{
|
|
DebugMsg ((DM_WARNING, IDS_REDIRECT_NO_LOCAL, m_szLocDisplayName, m_StatusRedir));
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_EXPAND_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString ( m_StatusRedir )
|
|
);
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
//we have found out the path stored in the registry, so compare it with
|
|
//the saved path
|
|
if (bCurrentPathValid && (0 == _wcsicmp (wszExpandedSavedFolderPath, wszExpandedCurrentPath)))
|
|
{
|
|
//all the paths are identical, so we are fine. save and quit
|
|
pLocalSettings->Save (m_szLocation, m_dwFlags, m_pSid, m_bValidGPO ? m_szGPOName : 0);
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot,
|
|
(m_bRemove || (m_dwFlags & REDIR_DONT_CARE))?FALSE:TRUE);
|
|
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT_INSYNC, m_szLocDisplayName,
|
|
wszExpandedNewFolderPath));
|
|
//however, it is possible that the folders have not been pinned.
|
|
//e.g. a roaming user who has already been redirected on one machine
|
|
//and now logs on to another machine for the first time.
|
|
DestStatus = GetCSCStatus (wszExpandedNewFolderPath);
|
|
if (ShareOnline == DestStatus)
|
|
{
|
|
|
|
PinStatus = PinIfNecessary (wszExpandedNewFolderPath, DestStatus);
|
|
if ( ERROR_SUCCESS != PinStatus )
|
|
DebugMsg((DM_VERBOSE, IDS_CSCPIN_FAIL,
|
|
m_szLocDisplayName, PinStatus));
|
|
|
|
CacheDesktopIni (wszExpandedNewFolderPath, DestStatus, PinFile);
|
|
}
|
|
m_StatusRedir = ERROR_SUCCESS;
|
|
goto Redir_CleanupAndQuit;
|
|
}
|
|
else //somebody has been messing with the registry
|
|
{
|
|
//use the path in the registry as the source path
|
|
//and perform redirection again
|
|
if (bCurrentPathValid)
|
|
wcscpy (wszExpandedSavedFolderPath, wszExpandedCurrentPath);
|
|
else
|
|
wcscpy(wszExpandedSavedFolderPath, L"???"); //to be on the safe side, since we don't want to use the saved path as the source for redirection
|
|
//if bCurrentPathValid is FALSE, this string will only be used in debug messages and error logs
|
|
}
|
|
}
|
|
|
|
if (g_bCSCEnabled)
|
|
{
|
|
SourceStatus = bCurrentPathValid ? GetCSCStatus (wszExpandedSavedFolderPath) : PathLocal;
|
|
DestStatus = GetCSCStatus (wszExpandedNewFolderPath);
|
|
if (ShareOffline == DestStatus ||
|
|
((m_dwFlags & REDIR_MOVE_CONTENTS) && ShareOffline == SourceStatus))
|
|
{
|
|
m_StatusRedir = ERROR_CSCSHARE_OFFLINE;
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_OFFLINE,
|
|
3,
|
|
m_szLocDisplayName,
|
|
wszExpandedSavedFolderPath,
|
|
wszExpandedNewFolderPath
|
|
);
|
|
goto Redir_KillChildAndLeave;
|
|
}
|
|
}
|
|
|
|
DebugMsg (( DM_VERBOSE, IDS_REDIRECT_PREVPATH,
|
|
pLocalSettings->m_szLastRedirectedPath,
|
|
wszExpandedSavedFolderPath));
|
|
DebugMsg (( DM_VERBOSE, IDS_REDIRECT_NEWPATH, m_szLocation,
|
|
wszExpandedNewFolderPath));
|
|
|
|
|
|
m_StatusRedir = PerformRedirection (
|
|
pFileDB,
|
|
bCurrentPathValid,
|
|
wszExpandedSavedFolderPath,
|
|
wszExpandedNewFolderPath,
|
|
SourceStatus,
|
|
DestStatus,
|
|
hUserToken
|
|
);
|
|
|
|
if (ERROR_SUCCESS == m_StatusRedir)
|
|
{
|
|
if (m_bRemove)
|
|
{
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot, FALSE);
|
|
pLocalSettings->Save (m_szLocation, REDIR_DONT_CARE, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (MyDocs == m_rID)
|
|
RestrictMyDocsRedirection (hUserToken, hKeyRoot,
|
|
(m_dwFlags & REDIR_DONT_CARE)?FALSE:TRUE);
|
|
|
|
pLocalSettings->Save (m_szLocation, m_dwFlags, m_pSid, m_bValidGPO ? m_szGPOName : 0);
|
|
}
|
|
}
|
|
else if (m_pChild && m_pChild->m_bFollowsParent) //if redirection was unsuccessful and this folder has children, prevent the redirection of the children if they are supposed to follow
|
|
{
|
|
PreventDescendantRedirection (m_StatusRedir);
|
|
}
|
|
|
|
goto Redir_CleanupAndQuit;
|
|
|
|
//the following code is executed whenever some fatal error occurs
|
|
//and we want to make sure that if this is a dir with a special
|
|
//descendant and the descendant is supposed to follow the parent,
|
|
//then we don't want to attempt redirection for the child.
|
|
Redir_KillChildAndLeave:
|
|
if (ERROR_SUCCESS != m_StatusRedir &&
|
|
(m_pChild && m_pChild->m_bFollowsParent))
|
|
PreventDescendantRedirection (m_StatusRedir);
|
|
|
|
Redir_CleanupAndQuit:
|
|
if (wszProcessedPath)
|
|
delete [] wszProcessedPath;
|
|
|
|
if ( ERROR_SUCCESS != m_StatusRedir )
|
|
{
|
|
HRESULT hrRsop;
|
|
|
|
hrRsop = pFileDB->AddPreservedPolicy( (WCHAR*) m_szDisplayName );
|
|
|
|
if ( FAILED( hrRsop ) )
|
|
{
|
|
pFileDB->GetRsopContext()->DisableRsop( hrRsop );
|
|
}
|
|
}
|
|
|
|
return m_StatusRedir;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::ShouldSaveExpandedPath
|
|
//
|
|
// Synopsis: Determines whether we need to store the expanded path in the
|
|
// registry or the unexpanded paths. See notes for additional
|
|
// details.
|
|
//
|
|
// Arguments: none.
|
|
//
|
|
// Returns: TRUE : if the expanded path should be stored.
|
|
// FALSE : otherwise.
|
|
//
|
|
// History: 5/1/2001 RahulTh created
|
|
//
|
|
// Notes:
|
|
// If the destination path is a homedir path, store the expanded path
|
|
// in the registry so that if msgina is unable to set the homedir
|
|
// variables and uses the local defaults instead, the shell doesn't
|
|
// end up thinking that MyDocs is local.
|
|
//
|
|
// If the destination path is a different kind of path, then store the
|
|
// expanded path if it has the %username% variable in it. Because if we
|
|
// don't then when a user's UPN changes and the user logs on with the new
|
|
// username, then the shell will expand the path using the new username.
|
|
// Now, normally this won't be a problem because we would have already
|
|
// done the rename operation. However, if for some reason we are
|
|
// not successful in that rename operation (say because we are
|
|
// invoked in limited foreground or because the server is offline)
|
|
// then the rename will not succeed. In this case, the shell will
|
|
// end up creating a new empty folder in that location whenever the
|
|
// user tries to access it. So the next time the user logs on,
|
|
// the new folder is already present and the rename fails with
|
|
// ERROR_ALREADY_EXISTS and we just end up pointing to the new
|
|
// location. The files stay in the old location. Therefore, we must
|
|
// not use the unexpanded path in SHSetFolderPath. However, we must
|
|
// store the unexpanded path in our local cache in order to
|
|
// successfully detect UPN changes.
|
|
//
|
|
// We do not want to store the expanded path unconditionally because
|
|
// in situations where the user is going back to a local location,
|
|
// especially the local userprofile location, we do not want to store
|
|
// the expanded path because it is not device independent and will cause
|
|
// problems for roaming users (e.g. a certain drive may not be available
|
|
// on all the machines so we should keep the path as %userprofile%\...
|
|
// rather than something like E:\Documents & Settings\...)
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
BOOL CRedirectInfo::ShouldSaveExpandedPath(void)
|
|
{
|
|
if (! m_szLocation || L'\0' == m_szLocation[0])
|
|
return FALSE;
|
|
|
|
// Detect the homedir case
|
|
if (IsHomedirPolicyPath(m_rID, m_szLocation, TRUE))
|
|
return TRUE;
|
|
|
|
//
|
|
// Check if the path contains the username variable.
|
|
// If it does then we should store the expanded paths. However regardless
|
|
// of everything else, if the path begins with %userprofile%, we should
|
|
// never store the expanded path because we will never be guaranteed a
|
|
// device independent path in that case and will run into all sorts of
|
|
// problems.
|
|
//
|
|
_wcslwr (m_szLocation);
|
|
// Compare the beginning of the two strings (the -1 is required to prevent comparing the terminating NULL)
|
|
if (0 == wcsncmp (m_szLocation, L"%userprofile%", sizeof(L"%userprofile%")/sizeof(WCHAR) - 1))
|
|
return FALSE;
|
|
if (wcsstr (m_szLocation, L"%username%"))
|
|
return TRUE;
|
|
|
|
//
|
|
// If we are here, we did not meet any of the conditions required for
|
|
// storing the expanded paths. So we should just store the unexpanded paths
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::PerformRedirection
|
|
//
|
|
// Synopsis: performs the nitty gritty of redirection including copying
|
|
// files, updating the registry, logging events etc.
|
|
//
|
|
// Arguments: [in] pFileDB : the CFileDB structure that is used throughout
|
|
// [in] bSourceValid : if pwszSource contains a valid path
|
|
// [in] pwszSource : source path
|
|
// [in] pwszDest : Destination path
|
|
// [in] StatusFrom : CSC status of the source share
|
|
// [in] StatusTo : CSC status of the destination share
|
|
//
|
|
// Returns: ERROR_SUCCESS if everything was successful. An error code
|
|
// otherwise.
|
|
//
|
|
// History: 11/21/1998 RahulTh created
|
|
// 12/13/2000 RahulTh Special cased homedir redirection to
|
|
// prevent security checks and store expanded
|
|
// paths in the registry (see comments within the function)
|
|
//
|
|
// Notes: this functions expects both shares to be online when it is
|
|
// invoked.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD CRedirectInfo::PerformRedirection (CFileDB * pFileDB,
|
|
BOOL bSourceValid,
|
|
WCHAR * pwszSource,
|
|
WCHAR * pwszDest,
|
|
SHARESTATUS StatusFrom,
|
|
SHARESTATUS StatusTo,
|
|
HANDLE hUserToken
|
|
)
|
|
{
|
|
BOOL bCheckOwner;
|
|
BOOL bMoveContents;
|
|
BOOL bIsHomeDirRedirection = FALSE; // Tracks if this is a homedir redirection policy
|
|
DWORD Status;
|
|
int iResultCompare;
|
|
WCHAR * pwszSkipSubdir;
|
|
int csidl;
|
|
BOOL bStatus;
|
|
DWORD PinStatus;
|
|
HRESULT hResult = S_OK;
|
|
CCopyFailData CopyFailure;
|
|
|
|
//
|
|
// We need to track if this is a homedir redirection policy because
|
|
// of 2 reasons:
|
|
// 1) In homedir redirection, security checks are skipped.
|
|
// 2) In homedir redirection, the expanded path is stored in the registry. This is
|
|
// to prevent problems that might occur if the homedir variables cannot be
|
|
// set by msgina and end up pointing to the local userprofile. In this case,
|
|
// we do not want the shell to start pointing MyDocs to the local location.
|
|
//
|
|
bIsHomeDirRedirection = IsHomedirPolicyPath(m_rID, m_szLocation, TRUE);
|
|
|
|
csidl = pFileDB->RegValueCSIDLFromFolderName (m_szFolderRelativePath);
|
|
|
|
// Skip security check for homedir redirection.
|
|
bCheckOwner = ((m_dwFlags & REDIR_SETACLS) && (!bIsHomeDirRedirection)) ? TRUE : FALSE;
|
|
bMoveContents = m_dwFlags & REDIR_MOVE_CONTENTS ? TRUE : FALSE;
|
|
|
|
if (bSourceValid)
|
|
{
|
|
Status = ComparePaths (pwszSource, pwszDest, &iResultCompare);
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
{
|
|
//this is very unlikely
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_REDIRECT_FAIL,
|
|
4,
|
|
m_szLocDisplayName,
|
|
StatusToString (Status),
|
|
pwszSource,
|
|
pwszDest
|
|
);
|
|
return Status;
|
|
}
|
|
|
|
if (0 == iResultCompare)
|
|
{
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT_INSYNC, m_szLocDisplayName,
|
|
pwszDest));
|
|
//however, it is possible that the folders have not been pinned.
|
|
//e.g. a roaming user who has already been redirected on one machine
|
|
//and now logs on to another machine for the first time.
|
|
if (ShareOnline == StatusTo)
|
|
{
|
|
|
|
PinStatus = PinIfNecessary (pwszDest, StatusTo);
|
|
if ( ERROR_SUCCESS != PinStatus )
|
|
DebugMsg((DM_VERBOSE, IDS_CSCPIN_FAIL,
|
|
m_szLocDisplayName, PinStatus));
|
|
|
|
CacheDesktopIni (pwszDest, StatusTo, PinFile);
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//it is okay to redirect to a path that is a descendant of the current
|
|
//path if we are not moving contents
|
|
if (bMoveContents && (-1 == iResultCompare))
|
|
{
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_REDIRECT_RECURSE,
|
|
4,
|
|
m_szLocDisplayName,
|
|
m_szLocation,
|
|
pwszSource,
|
|
pwszDest
|
|
);
|
|
|
|
return ERROR_REQUEST_ABORTED;
|
|
}
|
|
}
|
|
|
|
Status = pFileDB->CreateRedirectedFolderPath (pwszSource, pwszDest,
|
|
bSourceValid,
|
|
bCheckOwner, bMoveContents);
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
{
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_FOLDER_CREATE_FAIL,
|
|
4,
|
|
m_szLocDisplayName,
|
|
StatusToString (Status),
|
|
m_szLocation,
|
|
pwszDest
|
|
);
|
|
return Status;
|
|
}
|
|
|
|
//now we know that both the source and destinations paths exist
|
|
//so make sure that they are not the same path in different formats
|
|
//this is an expensive function as it involves creation and deletion of
|
|
//multiple files over the network. so we invoke it only if absolutely
|
|
//necessary
|
|
if (bSourceValid && bMoveContents)
|
|
{
|
|
Status = CheckIdenticalSpecial (pwszSource, pwszDest, &iResultCompare);
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
{
|
|
//this is very unlikely...
|
|
gpEvents->Report (
|
|
EVENT_FDEPLOY_REDIRECT_FAIL,
|
|
4,
|
|
m_szLocDisplayName,
|
|
StatusToString (Status),
|
|
pwszSource,
|
|
pwszDest
|
|
);
|
|
return Status;
|
|
}
|
|
|
|
if (0 == iResultCompare)
|
|
{
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT_INSYNC, m_szLocDisplayName,
|
|
pwszDest));
|
|
|
|
//
|
|
// The paths are the same but in different formats (or perhaps
|
|
// through different shares. So at least update the registry to
|
|
// point to the new path.
|
|
//
|
|
hResult = SHSetFolderPath(csidl | CSIDL_FLAG_DONT_UNEXPAND,
|
|
hUserToken,
|
|
0,
|
|
ShouldSaveExpandedPath() ? pwszDest : m_szLocation);
|
|
Status = GetWin32ErrFromHResult (hResult);
|
|
|
|
//
|
|
// This basically should never fail. But do we want to try to delete the
|
|
// copied files if it does? Or the wacked CSC database?
|
|
//
|
|
if ( Status != ERROR_SUCCESS )
|
|
{
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_REGSET_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString( Status ) );
|
|
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
//we were successul.
|
|
//first, rename the local CSC cache.
|
|
if (m_pChild)
|
|
pwszSkipSubdir = m_pChild->m_szLocDisplayName;
|
|
else
|
|
pwszSkipSubdir = NULL;
|
|
MoveDirInCSC (pwszSource, pwszDest, pwszSkipSubdir,
|
|
StatusFrom, StatusTo, TRUE, TRUE);
|
|
if (g_bCSCEnabled && ShareOnline == StatusFrom)
|
|
{
|
|
DeleteCSCFileTree (pwszSource, pwszSkipSubdir, TRUE);
|
|
DeleteCSCShareIfEmpty (pwszSource, StatusFrom);
|
|
}
|
|
|
|
//Also, it is possible that the folders have not been pinned.
|
|
//e.g. a roaming user who has already been redirected on one
|
|
//machine and now logs on to another machine for the first time.
|
|
if (ShareOnline == StatusTo)
|
|
{
|
|
|
|
PinStatus = PinIfNecessary (pwszDest, StatusTo);
|
|
if ( ERROR_SUCCESS != PinStatus )
|
|
DebugMsg((DM_VERBOSE, IDS_CSCPIN_FAIL,
|
|
m_szLocDisplayName, PinStatus));
|
|
|
|
CacheDesktopIni (pwszDest, StatusTo, PinFile);
|
|
}
|
|
|
|
//report this event and return. there is
|
|
//nothing more to be done here.
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_REDIRECT,
|
|
3,
|
|
m_szLocDisplayName,
|
|
bSourceValid ? pwszSource : L"???",
|
|
pwszDest );
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSourceValid && bMoveContents)
|
|
{
|
|
DebugMsg ((DM_VERBOSE, IDS_REDIRECT_COPYON, m_szLocDisplayName));
|
|
|
|
//
|
|
// Exclude any special descendants when
|
|
// doing file copies and deletes. Similarly for Programs and
|
|
// Startup
|
|
//
|
|
|
|
if (m_pChild)
|
|
pwszSkipSubdir = m_pChild->m_szLocDisplayName;
|
|
else
|
|
pwszSkipSubdir = NULL;
|
|
|
|
Status = pFileDB->CopyFileTree( pwszSource,
|
|
pwszDest,
|
|
pwszSkipSubdir,
|
|
StatusFrom,
|
|
StatusTo,
|
|
TRUE,
|
|
&CopyFailure );
|
|
|
|
//it is necessary to do the following for 2 reasons:
|
|
//(a) the user may have dirty files in the cache. these don't get
|
|
// moved above.
|
|
//(b) the files may have already been moved on the network by the
|
|
// the extension when the user logged on from another machine
|
|
// therefore, the cache on the second machine never gets updated
|
|
//
|
|
//we only try our best to move the files here. Errors are ignored.
|
|
if (ERROR_SUCCESS == Status)
|
|
{
|
|
MoveDirInCSC (pwszSource, pwszDest,
|
|
pwszSkipSubdir,
|
|
StatusFrom, StatusTo, FALSE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
//the copy failed. We might have failed halfway and left the cache
|
|
//in an inconsistent state, so rollback all the cached entries
|
|
MoveDirInCSC (pwszDest, pwszSource, pwszSkipSubdir,
|
|
StatusTo, StatusFrom, TRUE, TRUE);
|
|
}
|
|
|
|
|
|
|
|
if ( ERROR_SUCCESS != Status )
|
|
{
|
|
if (! CopyFailure.IsCopyFailure())
|
|
{
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_MOVE_FAIL,
|
|
5,
|
|
m_szLocDisplayName,
|
|
StatusToString( Status ),
|
|
m_szLocation,
|
|
pwszSource,
|
|
pwszDest );
|
|
}
|
|
else
|
|
{
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_COPY_FAIL,
|
|
7,
|
|
m_szLocDisplayName,
|
|
StatusToString( Status ),
|
|
m_szLocation,
|
|
pwszSource,
|
|
pwszDest,
|
|
CopyFailure.GetSourceName(),
|
|
CopyFailure.GetDestName() );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugMsg((DM_VERBOSE, IDS_REDIRECT_COPYOFF, m_szLocDisplayName));
|
|
}
|
|
|
|
//
|
|
// Look at the comments for ShouldSaveExpandedPath() for details on why we
|
|
// sometimes need to store expanded paths.
|
|
//
|
|
hResult = SHSetFolderPath(csidl | CSIDL_FLAG_DONT_UNEXPAND,
|
|
hUserToken,
|
|
0,
|
|
ShouldSaveExpandedPath() ? pwszDest : m_szLocation);
|
|
Status = GetWin32ErrFromHResult (hResult);
|
|
|
|
|
|
if ( Status != ERROR_SUCCESS )
|
|
{
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_REGSET_FAIL,
|
|
2,
|
|
m_szLocDisplayName,
|
|
StatusToString( Status ) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
if (!m_bRemove)
|
|
{
|
|
//
|
|
// hack to work around a shell problem.
|
|
//
|
|
// Pin the folder so that the shell never gets an error when
|
|
// trying to resolve this path. This will prevent it from
|
|
// reverting to a temporary local path.
|
|
//
|
|
// For now just call pin/unpin APIs for any unc style path. Not
|
|
// really much value in checking first to see if the share is
|
|
// cacheable.
|
|
//
|
|
|
|
if ( bSourceValid &&
|
|
(L'\\' == pwszSource[0]) &&
|
|
(L'\\' == pwszSource[1]) )
|
|
{
|
|
CSCUnpinFile( pwszSource,
|
|
FLAG_CSC_HINT_COMMAND_ALTER_PIN_COUNT,
|
|
NULL, NULL, NULL );
|
|
CacheDesktopIni (pwszSource, StatusFrom, UnpinFile);
|
|
}
|
|
|
|
if ( (L'\\' == pwszDest[0]) &&
|
|
(L'\\' == pwszDest[1]) )
|
|
{
|
|
PinStatus = PinIfNecessary (pwszDest, StatusTo);
|
|
|
|
if ( ERROR_SUCCESS != PinStatus )
|
|
DebugMsg((DM_VERBOSE, IDS_CSCPIN_FAIL,
|
|
m_szLocDisplayName, PinStatus));
|
|
|
|
CacheDesktopIni (pwszDest, StatusTo, PinFile);
|
|
}
|
|
}
|
|
|
|
//the contents were moved. now redirect any special children
|
|
//this ensures that deletions (if any) in the child folders,
|
|
//are performed first. thus deletion of this folder won't fail
|
|
//due to existence of its children within it.
|
|
//should not check for m_pChild->m_bFollowsParent here as
|
|
//the child may currently lie under this folder and if we do
|
|
//not perform the redirection of the child here, we might have
|
|
//problems deleting this folder even when we should not have any
|
|
//problems
|
|
if (m_pChild)
|
|
{
|
|
Status = m_pChild->Redirect (pFileDB->_hUserToken,
|
|
pFileDB->_hkRoot, pFileDB);
|
|
|
|
}
|
|
|
|
//note : contents of the source should not be deleted if this is a
|
|
// policy removal
|
|
if ( bSourceValid && bMoveContents)
|
|
{
|
|
Status = ERROR_SUCCESS;
|
|
|
|
//leave the contents on the server if this is a policy removal.
|
|
//also leave the contents on the server when moving from
|
|
//a network to a local location, so that subsequent redirections
|
|
//from other workstations will get all the contents back to local.
|
|
if (!m_bRemove && (!IsPathLocal(pwszDest) || IsPathLocal(pwszSource)))
|
|
{
|
|
//
|
|
// This could fail because of ACLing. We ignore any failures here.
|
|
//
|
|
DebugMsg((DM_VERBOSE, IDS_REDIRECT_DELETE,
|
|
m_szLocDisplayName, pwszSource));
|
|
|
|
Status = pFileDB->DeleteFileTree( pwszSource,
|
|
pwszSkipSubdir
|
|
);
|
|
if ( ERROR_SUCCESS == Status )
|
|
{
|
|
//DeleteFileTree does not remove the top level node passed to it.
|
|
// Delete the top level node only if it is not the user's home
|
|
// directory
|
|
const WCHAR * pwszHomeDir = NULL;
|
|
DWORD dwHomeDirStatus = ERROR_SUCCESS;
|
|
|
|
pwszHomeDir = gUserInfo.GetHomeDir(dwHomeDirStatus);
|
|
|
|
if (NULL == pwszHomeDir || 0 != lstrcmpi (pwszHomeDir, pwszSource))
|
|
{
|
|
//clear the attributes before deleting.
|
|
SetFileAttributes (pwszSource,
|
|
FILE_ATTRIBUTE_NORMAL);
|
|
if ( ! RemoveDirectory( pwszSource ) )
|
|
{
|
|
Status = GetLastError();
|
|
DebugMsg((DM_VERBOSE, IDS_DIRDEL_FAIL,
|
|
pwszSource, Status));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( Status != ERROR_SUCCESS )
|
|
DebugMsg((DM_WARNING, IDS_REDIRECT_DEL_FAIL,
|
|
pwszSource, m_szLocDisplayName,
|
|
m_szLocation, Status));
|
|
}
|
|
|
|
//but we always clean up the CSC cache irrespective of whether it is a
|
|
//policy removal or not because folder redirection should be as transparent
|
|
//to the user as possible and it would be annoying for the user to get
|
|
//CSC notifications for shares that are no longer used as redirection
|
|
//targets.
|
|
if (g_bCSCEnabled && ShareOnline == StatusFrom)
|
|
{
|
|
DeleteCSCFileTree (pwszSource, pwszSkipSubdir, TRUE);
|
|
DeleteCSCShareIfEmpty (pwszSource, StatusFrom);
|
|
}
|
|
|
|
}
|
|
|
|
gpEvents->Report(
|
|
EVENT_FDEPLOY_FOLDER_REDIRECT,
|
|
3,
|
|
m_szLocDisplayName,
|
|
bSourceValid ? pwszSource : L"???",
|
|
pwszDest );
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: PreventRedirection
|
|
//
|
|
// Synopsis: this function prevents the redirection code from attempting
|
|
// redirection. Also prevents redirection of any of the child
|
|
// folders.
|
|
//
|
|
// Arguments: [in] Status : the error code indicating the cause of failure.
|
|
//
|
|
// Returns: nothing. It will always succeed.
|
|
//
|
|
// History: 9/20/1999 RahulTh created
|
|
//
|
|
// Notes: if the pre-processing step that handles the user name change
|
|
// fails for some reason, this function is invoked so that
|
|
// any attempt at applying simultaneous policy changes is thwarted
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void CRedirectInfo::PreventRedirection (DWORD Status)
|
|
{
|
|
m_bRedirectionAttempted = TRUE;
|
|
m_StatusRedir = Status;
|
|
|
|
if (m_pChild)
|
|
PreventDescendantRedirection (Status);
|
|
|
|
return;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: PreventDescendantRedirection
|
|
//
|
|
// Synopsis: this function invalidates the data in the children so that
|
|
// the client extension will not try to redirect them
|
|
// this is necessary to prevent redirection of children if the
|
|
// redirection of the parents failed.
|
|
//
|
|
// Arguments: [in] Status : the error code indicating the cause of failure
|
|
//
|
|
// Returns: nothing. it will always succeed.
|
|
//
|
|
// History: 11/21/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void CRedirectInfo::PreventDescendantRedirection (DWORD Status)
|
|
{
|
|
if (! m_pChild) //nothing to do if this is not a parent
|
|
return;
|
|
|
|
m_pChild->m_bRedirectionAttempted = TRUE;
|
|
m_pChild->m_StatusRedir = Status;
|
|
|
|
//disable Startup too if start menu failed.
|
|
if (StartMenu == m_rID)
|
|
{
|
|
m_pChild->m_pChild->m_bRedirectionAttempted = TRUE;
|
|
m_pChild->m_pChild->m_StatusRedir = Status;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::UpdateDescendant
|
|
//
|
|
// Synopsis: fills up the internal variables of a descendant object
|
|
// if it is supposed to follow the parent by using the
|
|
// data stored in the parent object
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: nothing
|
|
//
|
|
// History: 10/6/1998 RahulTh created
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void CRedirectInfo::UpdateDescendant (void)
|
|
{
|
|
DWORD len;
|
|
WCHAR * szLoc = NULL;
|
|
|
|
if (!m_pParent) //this is not a special descendant
|
|
goto UpdateEnd;
|
|
|
|
if (!(m_dwFlags & REDIR_FOLLOW_PARENT))
|
|
goto UpdateEnd; //nothing to do. this descendant is not supposed to follow the parent
|
|
|
|
if (!m_pParent->m_fDataValid || (m_pParent->m_dwFlags & REDIR_DONT_CARE))
|
|
{
|
|
m_fDataValid = m_pParent->m_fDataValid;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
goto UpdateEnd;
|
|
}
|
|
|
|
m_fDataValid = m_pParent->m_fDataValid;
|
|
|
|
m_bFollowsParent = TRUE;
|
|
|
|
//if we are here, policy has been specified for the parent
|
|
len = lstrlen (m_szLocDisplayName) + lstrlen (m_pParent->m_szLocation) + 2; //one extra for the backslash
|
|
if (m_cbLocSize < len)
|
|
{
|
|
//we need to allocate memory
|
|
szLoc = new WCHAR [len];
|
|
if (!szLoc)
|
|
{
|
|
//out of memory. cannot derive info. from parent. will have to ignore this GPO
|
|
DebugMsg((DM_VERBOSE, IDS_DERIVEINFO_ERROR, m_szLocDisplayName));
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
m_fDataValid = FALSE;
|
|
goto UpdateEnd;
|
|
}
|
|
if (m_cbLocSize)
|
|
delete [] m_szLocation;
|
|
m_szLocation = szLoc;
|
|
m_cbLocSize = len;
|
|
}
|
|
|
|
if (m_pSid)
|
|
delete [] ((BYTE*) m_pSid);
|
|
MySidCopy (&m_pSid, m_pParent->m_pSid);
|
|
|
|
//copy the data
|
|
//first get the settings
|
|
m_dwFlags = m_pParent->m_dwFlags & (REDIR_SETACLS | REDIR_MOVE_CONTENTS | REDIR_RELOCATEONREMOVE);
|
|
lstrcpy (m_szLocation, m_pParent->m_szLocation);
|
|
len = lstrlen (m_szLocation);
|
|
if (len > 0 && L'\\' != m_szLocation[len - 1])
|
|
lstrcat (m_szLocation, L"\\"); //add a \ only if the parent's path is not
|
|
//terminated with a \. Otherwise, we will
|
|
//end up with 2 \ in the child's path which
|
|
//will land SHGetFolderPath into trouble
|
|
//after the redirection is done.
|
|
lstrcat (m_szLocation, m_szLocDisplayName); //use the localized folder name
|
|
m_bRemove = m_pParent->m_bRemove;
|
|
|
|
UpdateEnd:
|
|
return;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::operator=
|
|
//
|
|
// Synopsis: overloaded assignment operator used for merging
|
|
//
|
|
// Arguments: standard
|
|
//
|
|
// Returns: standard
|
|
//
|
|
// History: 10/6/1998 RahulTh created
|
|
//
|
|
// Notes: DO NOT copy the values of m_bRedirectionAttempted and
|
|
// m_StatusRedir in this function.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
CRedirectInfo& CRedirectInfo::operator= (const CRedirectInfo& ri)
|
|
{
|
|
WCHAR * szLoc = 0;
|
|
DWORD Status;
|
|
PSID Sid;
|
|
|
|
ASSERT (m_rID == ri.m_rID);
|
|
|
|
if (!ri.m_fDataValid)
|
|
goto AssignEnd;
|
|
|
|
if ((ri.m_dwFlags & REDIR_FOLLOW_PARENT) && MyPics == m_rID)
|
|
{
|
|
m_fDataValid = ri.m_fDataValid;
|
|
m_dwFlags = REDIR_FOLLOW_PARENT;
|
|
m_bRemove = ri.m_bRemove;
|
|
if (m_bValidGPO = ri.m_bValidGPO) //note:this IS an assignment -- not a comparison
|
|
wcscpy (m_szGPOName, ri.m_szGPOName);
|
|
else
|
|
m_szGPOName[0] = L'\0';
|
|
|
|
goto AssignEnd;
|
|
}
|
|
else if ((ri.m_dwFlags & (REDIR_DONT_CARE | REDIR_FOLLOW_PARENT)))
|
|
{
|
|
//REDIR_FOLLOW_PARENT will be set only if UpdateDescendant ran out of memory
|
|
//in any case, we will have to ignore the policy
|
|
|
|
//note that we have to special case My Pics above because UpdateDescendant
|
|
//is called for My Pics after all the policies have been looked at
|
|
//thus it has not been called at this point yet.
|
|
|
|
//the reason we call UpdateDescendant for My Pictures after looking at
|
|
//all the policies because it is possible to specify "Follow My Docs"
|
|
//in one policy and specify the location of My Docs in another policy
|
|
if (!m_fDataValid)
|
|
{
|
|
m_fDataValid = ri.m_fDataValid;
|
|
m_dwFlags = REDIR_DONT_CARE;
|
|
m_bRemove = ri.m_bRemove;
|
|
if (m_bValidGPO = ri.m_bValidGPO) //note: this IS an assignment -- not a comparison
|
|
wcscpy (m_szGPOName, ri.m_szGPOName);
|
|
else
|
|
m_szGPOName[0] = L'\0';
|
|
}
|
|
|
|
goto AssignEnd; //ignore. no policy settings for the GPO being merged.
|
|
}
|
|
|
|
//note: in the following code... before modifying any of the data
|
|
//we must make sure that we can get memory for all of the members
|
|
//if we fail for even one of them and we have already changed the rest
|
|
//we can run into an inconsistent state. Therefore, we first allocate
|
|
//all the required memory and then actually proceed with the copy.
|
|
Sid = 0;
|
|
Status = MySidCopy (&Sid, ri.m_pSid);
|
|
if (ERROR_SUCCESS != Status)
|
|
{
|
|
DebugMsg ((DM_VERBOSE, IDS_MERGE_FAILURE, m_szLocDisplayName));
|
|
goto AssignEnd;
|
|
}
|
|
|
|
if (m_cbLocSize < ri.m_cbLocSize)
|
|
{
|
|
szLoc = new WCHAR [ri.m_cbLocSize];
|
|
if (!szLoc)
|
|
{
|
|
//we could not obtain memory to store the new path.
|
|
//we will have to ignore this policy
|
|
DebugMsg ((DM_VERBOSE, IDS_MERGE_FAILURE, m_szLocDisplayName));
|
|
delete [] ((BYTE*) Sid); //do not do this at the end. The same memory will be used by m_pSid.
|
|
goto AssignEnd;
|
|
}
|
|
if (m_cbLocSize) delete [] m_szLocation;
|
|
m_szLocation = szLoc;
|
|
m_cbLocSize = ri.m_cbLocSize;
|
|
}
|
|
|
|
//now we have the required memory, so we won't fail.
|
|
//fill in the data.
|
|
if (m_pSid)
|
|
delete [] ((BYTE*) m_pSid);
|
|
m_pSid = Sid;
|
|
lstrcpy (m_szLocation, ri.m_szLocation);
|
|
m_dwFlags = ri.m_dwFlags & (REDIR_SETACLS | REDIR_MOVE_CONTENTS | REDIR_RELOCATEONREMOVE);
|
|
m_bRemove = ri.m_bRemove;
|
|
m_bFollowsParent = ri.m_bFollowsParent;
|
|
m_fDataValid = ri.m_fDataValid;
|
|
if (m_bValidGPO = ri.m_bValidGPO) //note: this IS an assignment not a comparison
|
|
wcscpy (m_szGPOName, ri.m_szGPOName);
|
|
else
|
|
m_szGPOName[0] = L'\0';
|
|
|
|
AssignEnd:
|
|
return *this;
|
|
}
|
|
|
|
//+--------------------------------------------------------------------------
|
|
//
|
|
// Member: CRedirectInfo::ComputeEffectivePolicyRemoval
|
|
//
|
|
// Synopsis: tries to find out if the removal of a user from a group
|
|
// has caused a particular policy to be effectively removed
|
|
// for a particular user.
|
|
//
|
|
// Arguments: [pGPOList] : a list of GPOs still in effect for this user.
|
|
// if a GPO is effectively removed for this user, it
|
|
// has to figure in this list.
|
|
// [pFileDB] : pointer to the file DB structure
|
|
//
|
|
// Returns:
|
|
//
|
|
// History: 2/18/1999 RahulTh created
|
|
//
|
|
// Notes: this also detects cases where a user's group membership may
|
|
// not have changed but the policy no longer specifies any
|
|
// target for this group.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD CRedirectInfo::ComputeEffectivePolicyRemoval (
|
|
PGROUP_POLICY_OBJECT pDeletedGPOList,
|
|
PGROUP_POLICY_OBJECT pChangedGPOList,
|
|
CFileDB * pFileDB)
|
|
{
|
|
WCHAR pwszLocalPath[MAX_PATH];
|
|
WCHAR * pwszLocalIniFile = NULL;
|
|
PGROUP_POLICY_OBJECT pGPO;
|
|
WCHAR * pwszGPTIniFilePath = NULL;
|
|
DWORD Length = 0;
|
|
DWORD Status = ERROR_SUCCESS;
|
|
BOOL bStatus;
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA FindData;
|
|
WCHAR pwszDefault[] = L"*";
|
|
DWORD dwFlags;
|
|
WCHAR * pwszReturnedString = NULL;
|
|
DWORD Size = 0;
|
|
UNICODE_STRING StringW;
|
|
DWORD i;
|
|
PTOKEN_GROUPS pGroups;
|
|
WCHAR pwszSid[MAX_PATH]; //more than enough to store a sid.
|
|
BOOL bGPOInChangedList = FALSE;
|
|
|
|
//if the policy resultant is not set to DONT_CARE, it means that even if a
|
|
//policy has been effectively removed due to a group change or removal of a
|
|
//group from the advanced settings, some other policy has taken precedence
|
|
//and as a result, the policy resultant should remain the way it is.
|
|
if (! (m_dwFlags & REDIR_DONT_CARE))
|
|
{
|
|
Status = ERROR_SUCCESS;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
//there is no valid GPO stored in the per user per machine cache.
|
|
//so we cannot do much.
|
|
if (!gSavedSettings[m_rID].m_bValidGPO)
|
|
{
|
|
Status = ERROR_SUCCESS;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
//if the location of this folder was not specified by policy at last logon
|
|
//a group change cannot result in an effective policy removal for this folder.
|
|
if (gSavedSettings[m_rID].m_dwFlags & REDIR_DONT_CARE)
|
|
{
|
|
Status = ERROR_SUCCESS;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
//if we are here, then the folder was last redirected through policy and now
|
|
//either policy does not care, or a group change makes it seem so. If it is
|
|
//the latter, then we have to compute effective policy removal. Also, it is
|
|
//possible that the last policy application was a partial success and
|
|
//therefore a GPO that got deleted did not show up in either the changed
|
|
//GPO list or the deleted GPO list. We have to take into account that case
|
|
//too.
|
|
|
|
//first check if the GPO is present in the deleted GPO list.
|
|
for (pGPO = pDeletedGPOList; pGPO; pGPO = pGPO->pNext)
|
|
{
|
|
if (0 == _wcsicmp (gSavedSettings[m_rID].m_szGPOName, pGPO->szGPOName))
|
|
break;
|
|
}
|
|
|
|
if (!pGPO)
|
|
{
|
|
//if the policy isn't in the deleted GPO list, check if it is in the
|
|
//changed GPO list. If it isn't then this is a GPO that got deleted but did
|
|
//not show up in any of the lists because it never got fully applied.
|
|
//if it is, then either this is actually a don't care situation or it
|
|
//should be treated as policy removal because there was a group change.
|
|
|
|
for (pGPO = pChangedGPOList; pGPO; pGPO = pGPO->pNext)
|
|
{
|
|
if (0 == _wcsicmp (gSavedSettings[m_rID].m_szGPOName, pGPO->szGPOName))
|
|
break;
|
|
}
|
|
|
|
if (NULL != pGPO) //it is in the changed GPO list.
|
|
{
|
|
bGPOInChangedList = TRUE;
|
|
//get the path to the ini file on the sysvol.
|
|
Length = wcslen(pGPO->lpFileSysPath) + wcslen(GPT_SUBDIR) + wcslen (INIFILE_NAME) + 1;
|
|
pwszGPTIniFilePath = (WCHAR *) alloca( Length * sizeof(WCHAR) );
|
|
if ( ! pwszGPTIniFilePath )
|
|
{
|
|
Status = ERROR_OUTOFMEMORY;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
wcscpy( pwszGPTIniFilePath, pGPO->lpFileSysPath );
|
|
wcscat( pwszGPTIniFilePath, GPT_SUBDIR );
|
|
wcscat( pwszGPTIniFilePath, INIFILE_NAME );
|
|
}
|
|
}
|
|
|
|
|
|
//get the path to the locally cached copy of the ini file.
|
|
Length = wcslen (pFileDB->_pwszLocalPath) + wcslen (gSavedSettings[m_rID].m_szGPOName) + 6;
|
|
pwszLocalIniFile = (WCHAR *) alloca (Length * sizeof (WCHAR));
|
|
if (!pwszLocalIniFile)
|
|
{
|
|
Status = ERROR_OUTOFMEMORY;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
wcscpy( pwszLocalIniFile, pFileDB->_pwszLocalPath );
|
|
wcscat( pwszLocalIniFile, L"\\" );
|
|
wcscat( pwszLocalIniFile, gSavedSettings[m_rID].m_szGPOName );
|
|
wcscat( pwszLocalIniFile, L".ini" );
|
|
|
|
Status = ERROR_SUCCESS;
|
|
bStatus = FALSE;
|
|
if (bGPOInChangedList)
|
|
{
|
|
bStatus = CopyFile( pwszLocalIniFile, pwszGPTIniFilePath, FALSE );
|
|
}
|
|
if ( ! bStatus ) // Work off of the locally cached copy.
|
|
{
|
|
//try to use the cached version if any.
|
|
hFind = FindFirstFile( pwszLocalIniFile, &FindData );
|
|
if ( INVALID_HANDLE_VALUE != hFind )
|
|
{
|
|
Status = ERROR_SUCCESS;
|
|
FindClose( hFind );
|
|
}
|
|
else
|
|
{
|
|
Status = GetLastError();
|
|
}
|
|
}
|
|
|
|
//we don't have an ini file to work with, so we can't do much but quit.
|
|
if (ERROR_SUCCESS != Status)
|
|
goto CmpEffPolRem_End;
|
|
|
|
//now we have an ini file. so read the relevant info. from it.
|
|
//first grab the flags
|
|
Status = SafeGetPrivateProfileStringW (
|
|
L"FolderStatus",
|
|
m_szDisplayName,
|
|
pwszDefault,
|
|
&pwszReturnedString,
|
|
&Size,
|
|
pwszLocalIniFile
|
|
);
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
goto CmpEffPolRem_End;
|
|
|
|
if (L'*' == *pwszReturnedString)
|
|
{
|
|
//there are no settings for this folder. Possibly because
|
|
//someone changed the settings on the server.
|
|
//Treat it as a don't care.
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
//now grab the hex flags
|
|
StringW.Buffer = pwszReturnedString;
|
|
StringW.Length = wcslen (pwszReturnedString) * sizeof (WCHAR);
|
|
StringW.MaximumLength = StringW.Length + sizeof(WCHAR);
|
|
RtlUnicodeStringToInteger( &StringW, 16, &dwFlags );
|
|
|
|
//if this is a special descendant folder and it is supposed to follow
|
|
//the parent, we might first have to derive its settings from the
|
|
//parent and then proceed.
|
|
if (m_pParent && (dwFlags & REDIR_FOLLOW_PARENT))
|
|
{
|
|
//the check for m_pParent is redundant since non-descendant folders
|
|
//can never have this flag. but this has been added as a safety mechanism
|
|
//against ini file corruption
|
|
//we will have to derive the settings from the parent later on.
|
|
m_dwFlags = REDIR_FOLLOW_PARENT;
|
|
m_fDataValid = TRUE;
|
|
m_bFollowsParent = TRUE;
|
|
m_bRemove = TRUE;
|
|
m_bValidGPO = FALSE; //since this is a removal
|
|
m_szGPOName[0] = L'\0';
|
|
UpdateDescendant(); //derive the settings from the parent
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
if ((dwFlags & REDIR_DONT_CARE) ||
|
|
(m_bFollowsParent && (m_dwFlags & REDIR_DONT_CARE)))
|
|
{
|
|
//the policy has been changed to Don't Care. so it is not a removal.
|
|
//leave everything as is.
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
if (!(dwFlags & REDIR_RELOCATEONREMOVE))
|
|
{
|
|
//the choice is to orphan. so let it stay as don't care.
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
|
|
//
|
|
// If the GPO that was used for redirection the last time is not in the
|
|
// changed GPO list, then this is surely a policy removal.
|
|
// otherwise, it can either be a policy removal due to a group change (or
|
|
// a group's policy getting removed from the GPO or it can be just that
|
|
// the policy was changed to Don't care.
|
|
//
|
|
if (bGPOInChangedList)
|
|
{
|
|
//
|
|
// Check if the user is still a member of the group that was used for
|
|
// redirection.
|
|
//
|
|
pGroups = pFileDB->_pGroups;
|
|
for (i = 0, bStatus = FALSE;
|
|
i < pGroups->GroupCount && !bStatus;
|
|
i++
|
|
)
|
|
{
|
|
bStatus = RtlEqualSid (gSavedSettings[m_rID].m_psid, pGroups->Groups[i].Sid);
|
|
}
|
|
|
|
if (bStatus) //the user still belongs to that group.
|
|
{
|
|
//so perhaps the policy for this group was removed.
|
|
//make sure that this is the case.
|
|
Status = ERROR_INVALID_SID;
|
|
if (gSavedSettings[m_rID].m_psid)
|
|
{
|
|
pwszSid [0] = L'\0';
|
|
StringW.Length = 0;
|
|
StringW.MaximumLength = sizeof (pwszSid);
|
|
StringW.Buffer = pwszSid;
|
|
Status = RtlConvertSidToUnicodeString (&StringW, gSavedSettings[m_rID].m_psid, FALSE);
|
|
}
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
goto CmpEffPolRem_End;
|
|
|
|
Status = SafeGetPrivateProfileStringW (
|
|
m_szDisplayName,
|
|
StringW.Buffer,
|
|
pwszDefault,
|
|
&pwszReturnedString,
|
|
&Size,
|
|
pwszLocalIniFile
|
|
);
|
|
|
|
if (ERROR_SUCCESS != Status)
|
|
goto CmpEffPolRem_End;
|
|
|
|
if (0 != _wcsicmp(pwszReturnedString, pwszDefault))
|
|
{
|
|
//
|
|
// Policy exists for this folder so leave things the way they are.
|
|
// Ideally this is not possible and one should never enter this
|
|
// code path
|
|
//
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the user is no longer a member of the group, then this is clearly
|
|
// a case where the policy is effectively removed because the user was
|
|
// removed from a group that was used for redirection.
|
|
//
|
|
// At any rate, if we are here, then this is a policy removal so make the
|
|
// appropriate settings
|
|
//
|
|
if (m_cbLocSize && (m_cbLocSize < (Size = wcslen(L"%USERPROFILE%\\") + wcslen(m_szLocFolderRelativePath) + 1)))
|
|
{
|
|
delete [] m_szLocation;
|
|
m_cbLocSize = 0;
|
|
m_szLocation = new WCHAR [Size];
|
|
if (!m_szLocation)
|
|
{
|
|
Status = ERROR_OUTOFMEMORY;
|
|
goto CmpEffPolRem_End;
|
|
}
|
|
m_cbLocSize = Size;
|
|
}
|
|
wcscpy (m_szLocation, L"%USERPROFILE%\\");
|
|
wcscat (m_szLocation, m_szLocFolderRelativePath);
|
|
m_fDataValid = TRUE;
|
|
m_dwFlags = dwFlags;
|
|
m_bRemove = TRUE;
|
|
m_bValidGPO = FALSE; //since this is a removal.
|
|
m_szGPOName[0] = '\0';
|
|
DebugMsg((DM_VERBOSE, IDS_EFFECTIVE_REMOVE_POLICY, pGPO?pGPO->lpDisplayName:gSavedSettings[m_rID].m_szGPOName, m_szLocDisplayName));
|
|
|
|
CmpEffPolRem_End:
|
|
if (pwszReturnedString)
|
|
delete [] pwszReturnedString;
|
|
return Status;
|
|
}
|
|
|
|
CRedirectInfo::HasPolicy()
|
|
{
|
|
return ! ( m_dwFlags & REDIR_DONT_CARE );
|
|
}
|