Leaked source code of windows server 2003
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.
 
 
 
 
 
 

2440 lines
65 KiB

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name:
profmap.c
Abstract:
Implements profile mapping APIs, to move local profile ownership
from one user to another.
Author:
Jim Schmidt (jimschm) 27-May-1999
Revision History:
<alias> <date> <comments>
--*/
#include "pch.h"
#define PCOMMON_IMPL
#include "pcommon.h"
//
// Worker prototypes
//
DWORD
pRemapUserProfile (
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
);
BOOL
pLocalRemapAndMoveUserW (
IN DWORD Flags,
IN PCWSTR ExistingUser,
IN PCWSTR NewUser
);
VOID
pFixSomeSidReferences (
PSID ExistingSid,
PSID NewSid
);
VOID
pOurGetProfileRoot (
IN PCWSTR SidString,
OUT PWSTR ProfileRoot
);
#define REMAP_KEY_NAME L"$remap$"
//
// Implementation
//
BOOL
WINAPI
DllMain (
IN HINSTANCE hInstance,
IN DWORD dwReason,
IN LPVOID lpReserved
)
{
return TRUE;
}
/*++
Routine Description:
SmartLocalFree and SmartRegCloseKey are cleanup routines that ignore NULL
values.
Arguments:
Mem or Key - Specifies the value to clean up.
Return Value:
None.
--*/
VOID
SmartLocalFree (
PVOID Mem OPTIONAL
)
{
if (Mem) {
LocalFree (Mem);
}
}
VOID
SmartRegCloseKey (
HKEY Key OPTIONAL
)
{
if (Key) {
RegCloseKey (Key);
}
}
BOOL
WINAPI
pLocalRemapUserProfileW (
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
)
/*++
Routine Description:
pLocalRemapUserProfile begins the process of remapping a profile from one
SID to another. This function validates the caller's arguments, and then
calls pRemapUserProfile to do the work. Top-level exceptions are handled
here.
Arguments:
Flags - Specifies zero or more profile mapping flags.
SidCurrent - Specifies the SID of the user who's profile is going to be
remaped.
SidNew - Specifies the SID of the user who will own the profile.
Return Value:
TRUE if success, FALSE if failure. GetLastError provides failure code.
--*/
{
DWORD Error;
PWSTR CurrentSidString = NULL;
PWSTR NewSidString = NULL;
INT Order;
PWSTR p, q;
HANDLE hToken = NULL;
DWORD dwErr1 = ERROR_ACCESS_DENIED, dwErr2 = ERROR_ACCESS_DENIED;
DEBUGMSG((DM_VERBOSE, "========================================================="));
DEBUGMSG((
DM_VERBOSE,
"RemapUserProfile: Entering, Flags = <0x%x>, SidCurrent = <0x%x>, SidNew = <0x%x>",
Flags,
SidCurrent,
SidNew
));
if (!OpenThreadToken (GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken)) {
if (!OpenProcessToken (GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: OpenProcessToken failed with code %u", Error));
goto Exit;
}
}
if (!IsUserAnAdminMember (hToken)) {
Error = ERROR_ACCESS_DENIED;
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Caller is not an administrator"));
goto Exit;
}
#ifdef DBG
{
PSID DbgSid;
PWSTR DbgSidString;
DbgSid = GetUserSid (hToken);
if (OurConvertSidToStringSid (DbgSid, &DbgSidString)) {
DEBUGMSG ((DM_VERBOSE, "RemapAndMoveUserW: Caller's SID is %s", DbgSidString));
DeleteSidString (DbgSidString);
}
DeleteUserSid (DbgSid);
}
#endif
//
// Validate args
//
Error = ERROR_INVALID_PARAMETER;
if (!IsValidSid (SidCurrent)) {
DEBUGMSG((DM_WARNING, "RemapUserProfile: received invalid current user sid."));
goto Exit;
}
if (!IsValidSid (SidNew)) {
DEBUGMSG((DM_WARNING, "RemapUserProfile: received invalid new user sid."));
goto Exit;
}
//
// All arguments are valid. Lock the users and call a worker.
//
if (!OurConvertSidToStringSid (SidCurrent, &CurrentSidString)) {
Error = GetLastError();
DEBUGMSG((DM_WARNING, "RemapUserProfile: Can't stringify current sid."));
goto Exit;
}
if (!OurConvertSidToStringSid (SidNew, &NewSidString)) {
Error = GetLastError();
DEBUGMSG((DM_WARNING, "RemapUserProfile: Can't stringify new sid."));
goto Exit;
}
//
// SID arguments must be unique. We assume the OS uses the same character set
// to stringify a SID, even if something like a locale change happens in the
// middle of our code.
//
p = CurrentSidString;
q = NewSidString;
while (*p && *p == *q) {
p++;
q++;
}
Order = *p - *q;
if (!Order) {
DEBUGMSG((DM_WARNING, "RemapUserProfile: Both sids match (%s=%s)",
CurrentSidString, NewSidString));
Error = ERROR_INVALID_PARAMETER;
goto Exit;
}
ASSERT (lstrcmpi (CurrentSidString, NewSidString));
//
// Grab the user profile mutexes in wchar-sorted order. This eliminates
// a deadlock with another RemapUserProfile call.
//
if (Order < 0) {
dwErr1 = EnterUserProfileLock (CurrentSidString);
if (dwErr1 == ERROR_SUCCESS) {
dwErr2 = EnterUserProfileLock (NewSidString);
}
} else {
dwErr2 = EnterUserProfileLock (NewSidString);
if (dwErr2 == ERROR_SUCCESS) {
dwErr1 = EnterUserProfileLock (CurrentSidString);
}
}
if (dwErr1 != ERROR_SUCCESS || dwErr2 != ERROR_SUCCESS) {
Error = GetLastError();
DEBUGMSG((DM_WARNING, "RemapUserProfile: Failed to grab a user profile lock, error = %u", Error));
goto Exit;
}
__try {
Error = pRemapUserProfile (Flags, SidCurrent, SidNew);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Error = ERROR_NOACCESS;
DEBUGMSG((DM_WARNING, "RemapUserProfile: Exception thrown in PrivateRemapUserProfile."));
}
Exit:
if (hToken) {
CloseHandle (hToken);
}
if (CurrentSidString) {
if(dwErr1 == ERROR_SUCCESS) {
LeaveUserProfileLock (CurrentSidString);
}
DeleteSidString (CurrentSidString);
}
if (NewSidString) {
if(dwErr2 == ERROR_SUCCESS) {
LeaveUserProfileLock (NewSidString);
}
DeleteSidString (NewSidString);
}
SetLastError (Error);
return Error == ERROR_SUCCESS;
}
BOOL
GetNamesFromUserSid (
IN PCWSTR RemoteTo,
IN PSID Sid,
OUT PWSTR *User,
OUT PWSTR *Domain
)
/*++
Routine Description:
GetNamesFromUserSid obtains the user and domain name from a SID. The SID
must be a user account (not a group, printer, etc.).
Arguments:
RemoteTo - Specifies the computer to remote the call to
Sid - Specifies the SID to look up
User - Receives the user name. If non-NULL, the caller must free this
buffer with LocalFree.
Domain - Receives the domain name. If non-NULL, the caller must free the
buffer with LocalFree.
Return Value:
TRUE on success, FALSE on failure, GetLastError provides failure code.
--*/
{
DWORD UserSize = 256;
DWORD DomainSize = 256;
PWSTR UserBuffer = NULL;
PWSTR DomainBuffer = NULL;
DWORD Result = ERROR_SUCCESS;
BOOL b;
SID_NAME_USE use;
//
// Allocate initial buffers of 256 chars
//
UserBuffer = LocalAlloc (LPTR, UserSize * sizeof (WCHAR));
if (!UserBuffer) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Alloc error %u.", GetLastError()));
goto Exit;
}
DomainBuffer = LocalAlloc (LPTR, DomainSize * sizeof (WCHAR));
if (!DomainBuffer) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Alloc error %u.", GetLastError()));
goto Exit;
}
b = LookupAccountSid (
RemoteTo,
Sid,
UserBuffer,
&UserSize,
DomainBuffer,
&DomainSize,
&use
);
if (!b) {
Result = GetLastError();
if (Result == ERROR_NONE_MAPPED) {
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Account not found"));
goto Exit;
}
if (UserSize <= 256 && DomainSize <= 256) {
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Unexpected error %u", Result));
Result = ERROR_UNEXP_NET_ERR;
goto Exit;
}
//
// Try allocating new buffers
//
if (UserSize > 256) {
SmartLocalFree (UserBuffer);
UserBuffer = LocalAlloc (LPTR, UserSize * sizeof (WCHAR));
if (!UserBuffer) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Alloc error %u.", GetLastError()));
goto Exit;
}
}
if (DomainSize > 256) {
SmartLocalFree (DomainBuffer);
DomainBuffer = LocalAlloc (LPTR, DomainSize * sizeof (WCHAR));
if (!DomainBuffer) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Alloc error %u.", GetLastError()));
goto Exit;
}
}
//
// Try look up again
//
b = LookupAccountSid (
RemoteTo,
Sid,
UserBuffer,
&UserSize,
DomainBuffer,
&DomainSize,
&use
);
if (!b) {
//
// All attempts failed.
//
Result = GetLastError();
if (Result != ERROR_NONE_MAPPED) {
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: Unexpected error %u (2)", Result));
Result = ERROR_UNEXP_NET_ERR;
}
goto Exit;
}
}
//
// LookupAccountSid succeeded. Now verify that the accout type
// is correct.
//
if (use != SidTypeUser) {
DEBUGMSG((DM_WARNING, "GetNamesFromUserSid: SID specifies bad account type: %u", (DWORD) use));
Result = ERROR_NONE_MAPPED;
goto Exit;
}
ASSERT (Result == ERROR_SUCCESS);
Exit:
if (Result != ERROR_SUCCESS) {
SmartLocalFree (UserBuffer);
UserBuffer = NULL;
SmartLocalFree (DomainBuffer);
DomainBuffer = NULL;
SetLastError (Result);
}
*User = UserBuffer;
*Domain = DomainBuffer;
return (Result == ERROR_SUCCESS);
}
DWORD
pRemapUserProfile (
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
)
/*++
Routine Description:
pRemapUserProfile changes the security of a profile from one SID to
another. Upon completion, the original user will not have access to the
profile, and the new user will.
Arguments:
Flags - Specifies zero or more profile remap flags. Specify
REMAP_PROFILE_NOOVERWRITE to guarantee no existing user
setting is overwritten. Specify
REMAP_PROFILE_NOUSERNAMECHANGE to make sure the user name does
not change.
SidCurrent - Specifies the current user SID. This user must own the profile,
and upon completion, the user will not have a local profile.
SidNew - Specifies the new user SID. This user will own the profile
upon completion.
Return Value:
A Win32 status code.
--*/
{
PWSTR CurrentUser = NULL;
PWSTR CurrentDomain = NULL;
PWSTR CurrentSidString = NULL;
PWSTR NewUser = NULL;
PWSTR NewDomain = NULL;
PWSTR NewSidString = NULL;
DWORD Size;
DWORD Result = ERROR_SUCCESS;
INT UserCompare;
INT DomainCompare;
BOOL b;
HKEY hCurrentProfile = NULL;
HKEY hNewProfile = NULL;
HKEY hProfileList = NULL;
LONG rc;
DWORD Type;
BOOL CleanUpFailedCopy = FALSE;
DWORD Loaded;
UNICODE_STRING Unicode_String;
NTSTATUS Status;
//
// The caller must make sure we have valid args.
//
//
// Get the names for the user
//
b = GetNamesFromUserSid (NULL, SidCurrent, &CurrentUser, &CurrentDomain);
if (!b) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Current user SID is not a valid user"));
goto Exit;
}
b = GetNamesFromUserSid (NULL, SidNew, &NewUser, &NewDomain);
if (!b) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: New user SID is not a valid user"));
goto Exit;
}
//
// Compare them
//
UserCompare = lstrcmpi (CurrentUser, NewUser);
DomainCompare = lstrcmpi (CurrentDomain, NewDomain);
//
// Either the user or domain must be different. If the caller specifies
// REMAP_PROFILE_NOUSERNAMECHANGE, then user cannot be different.
//
if (UserCompare == 0 && DomainCompare == 0) {
//
// This case should not be possible.
//
ASSERT (FALSE);
Result = ERROR_INVALID_PARAMETER;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: User and domain names match for different SIDs"));
goto Exit;
}
if ((Flags & REMAP_PROFILE_NOUSERNAMECHANGE) && UserCompare != 0) {
DEBUGMSG((DM_WARNING, "pRemapUserProfile: User name can't change from %s to %s",
CurrentUser, NewUser));
Result = ERROR_BAD_USERNAME;
goto Exit;
}
//
// The SID change now makes sense. Let's change it. Start by
// obtaining a string version of the SID.
//
if (!OurConvertSidToStringSid (SidCurrent, &CurrentSidString)) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't stringify current sid."));
goto Exit;
}
if (!OurConvertSidToStringSid (SidNew, &NewSidString)) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't stringify new sid."));
goto Exit;
}
//
// Open the profile list key
//
rc = RegOpenKeyEx (HKEY_LOCAL_MACHINE, PROFILE_LIST_PATH, 0, KEY_READ|KEY_WRITE,
&hProfileList);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "PrivateRemapUserProfile: Can't open profile list key."));
goto Exit;
}
//
// Open the current user's profile list key. Then make sure the profile is not
// loaded, and get the profile directory.
//
rc = RegOpenKeyEx (hProfileList, CurrentSidString, 0, KEY_READ | KEY_WRITE, &hCurrentProfile);
if (rc != ERROR_SUCCESS) {
if (rc == ERROR_FILE_NOT_FOUND) {
Result = ERROR_NO_SUCH_USER;
} else {
Result = rc;
}
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't open current user's profile list key."));
goto Exit;
}
Size = sizeof(Loaded);
rc = RegQueryValueEx (hCurrentProfile, L"RefCount", NULL, &Type, (PBYTE) &Loaded, &Size);
if (rc != ERROR_SUCCESS || Type != REG_DWORD) {
DEBUGMSG((DM_VERBOSE, "pRemapUserProfile: Current user does not have a ref count."));
Loaded = 0;
}
if (Loaded) {
Result = ERROR_ACCESS_DENIED;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Current user profile is loaded."));
goto Exit;
}
//
// Now open the new user's key. If it already exists, then the
// caller can specify REMAP_PROFILE_NOOVERWRITE to make sure
// we don't blow away an existing profile setting.
//
rc = RegOpenKeyEx(hProfileList, NewSidString, 0, KEY_READ | KEY_WRITE, &hNewProfile);
if (rc == ERROR_SUCCESS) {
//
// Did the caller specify REMAP_PROFILE_NOOVERWRITE?
//
if (Flags & REMAP_PROFILE_NOOVERWRITE) {
Result = ERROR_USER_EXISTS;
DEBUGMSG((DM_VERBOSE, "pRemapUserProfile: Destination profile entry exists."));
goto Exit;
}
//
// Verify existing profile is not loaded
//
Size = sizeof(Loaded);
rc = RegQueryValueEx (hNewProfile, L"RefCount", NULL, &Type, (PBYTE) &Loaded, &Size);
if (rc != ERROR_SUCCESS || Type != REG_DWORD) {
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Existing destination user does not have a ref count."));
Loaded = 0;
}
if (Loaded) {
Result = ERROR_ACCESS_DENIED;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Existing destination user profile is loaded."));
goto Exit;
}
//
// Remove the key
//
RegCloseKey (hNewProfile);
hNewProfile = NULL;
rc = RegDelnode (hProfileList, NewSidString);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't reset new profile list key."));
goto Exit;
}
}
//
// Transfer contents of current user key to new user using NtRenameKey.
//
// If an error is encountered, we abandon the successful work above,
// which includes possibly deletion of an existing profile list key.
//
CleanUpFailedCopy = TRUE;
Unicode_String.Length = ByteCountW(NewSidString);
Unicode_String.MaximumLength = Unicode_String.Length + sizeof(WCHAR);
Unicode_String.Buffer = NewSidString;
Status = NtRenameKey(hCurrentProfile, &Unicode_String);
if (Status != STATUS_SUCCESS) {
Result = RtlNtStatusToDosError(Status);
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Error %d in renaming profile list entry.", Result));
goto Exit;
}
// Close the old key
RegCloseKey (hCurrentProfile);
hCurrentProfile = NULL;
//
// Now open the new key and update SID & GUID information in it
//
rc = RegOpenKeyEx(hProfileList, NewSidString, 0, KEY_READ | KEY_WRITE, &hNewProfile);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Error %d in opening new profile list entry.", Result));
goto Exit;
}
//
// Update new profile's SID
//
rc = RegSetValueEx (hNewProfile, L"SID", 0, REG_BINARY, SidNew, GetLengthSid (SidNew));
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Error %d setting new profile SID.", Result));
goto Exit;
}
//
// Delete GUID value & associated GUID key if it exists.
// It will get re-established on the next logon.
//
if (!DeleteProfileGuidSettings (hNewProfile)) {
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Error %d in deleting profile GUID settings"));
}
//
// Set security on the new key. We pass pNewSid and that is all
// CreateUserProfile needs. To get by arg checking, we throw in
// NewUser as the user name.
//
if (!UpdateProfileSecurity (SidNew)) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: UpdateProfileSecurity returned %u.", Result));
goto Exit;
}
//
// Success -- the profile was transferred and nothing went wrong
//
CleanUpFailedCopy = FALSE;
RegFlushKey (HKEY_LOCAL_MACHINE);
ASSERT (Result == ERROR_SUCCESS);
Exit:
SmartRegCloseKey (hCurrentProfile);
SmartRegCloseKey (hNewProfile);
if (CleanUpFailedCopy) {
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Backing out changes because of failure"));
RegDelnode (hProfileList, NewSidString);
}
SmartLocalFree (CurrentUser);
SmartLocalFree (CurrentDomain);
SmartLocalFree (NewUser);
SmartLocalFree (NewDomain);
DeleteSidString (CurrentSidString);
DeleteSidString (NewSidString);
SmartRegCloseKey (hProfileList);
return Result;
}
BOOL
WINAPI
RemapUserProfileW (
IN PCWSTR Computer,
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
)
/*++
Routine Description:
RemapUserProfileW is the exported API. It calls the local version via RPC.
Arguments:
Computer - Specifies the computer to remote the API to. If NULL or ".",
the API will run locally. If non-NULL, the API will run on
the remote computer.
Flags - Specifies the profile mapping flags. See implementation above
for details.
SidCurrent - Specifies the SID of the user who owns the profile.
SidNew - Specifies the SID of the user who will own the profile after
the API completes.
Return Value:
TRUE if success, FALSE if failure. GetLastError provides the failure code.
--*/
{
DWORD Result = ERROR_SUCCESS;
HANDLE RpcHandle;
if (!IsValidSid (SidCurrent)) {
DEBUGMSG((DM_WARNING, "RemapUserProfileW: received invalid current user sid."));
SetLastError (ERROR_INVALID_SID);
return FALSE;
}
if (!IsValidSid (SidNew)) {
DEBUGMSG((DM_WARNING, "RemapUserProfileW: received invalid new user sid."));
SetLastError (ERROR_INVALID_SID);
return FALSE;
}
//
// We do not support profmap to run in remote computer until we move
// the ProfMapApi rpc interface to a LocalService hosted process
//
if (Computer) {
DEBUGMSG((DM_WARNING, "RemapUserProfileW: received computer name"));
SetLastError (ERROR_INVALID_PARAMETER);
return FALSE;
}
__try {
if (pLocalRemapUserProfileW (Flags, SidCurrent, SidNew)) {
Result = ERROR_SUCCESS;
} else {
Result = GetLastError();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Result = ERROR_NOACCESS;
}
if (Result != ERROR_SUCCESS) {
SetLastError (Result);
return FALSE;
}
return TRUE;
}
BOOL
WINAPI
RemapUserProfileA (
IN PCSTR Computer,
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
)
/*++
Routine Description:
RemapUserProfileA is a wrapper to RemapUserProfileW.
Arguments:
Computer - Specifies the computer to remote the API to. If NULL or ".",
the API will run locally. If non-NULL, the API will run on
the remote computer.
Flags - Specifies the profile mapping flags. See implementation above
for details.
SidCurrent - Specifies the SID of the user who owns the profile.
SidNew - Specifies the SID of the user who will own the profile after
the API completes.
Return Value:
TRUE if success, FALSE if failure. GetLastError provides the failure code.
--*/
{
PWSTR UnicodeComputer;
BOOL b;
DWORD Err;
if (!Computer) {
UnicodeComputer = NULL;
} else {
UnicodeComputer = ProduceWFromA (Computer);
if (!UnicodeComputer) {
return FALSE;
}
}
b = RemapUserProfileW (UnicodeComputer, Flags, SidCurrent, SidNew);
Err = GetLastError();
SmartLocalFree (UnicodeComputer);
SetLastError (Err);
return b;
}
BOOL
WINAPI
InitializeProfileMappingApi (
VOID
)
/*++
Routine Description:
InitializeProfileMappingApi is called by winlogon.exe to initialize the RPC
server interfaces.
Arguments:
None.
Return Value:
TRUE if successful, FALSE otherwise. GetLastError provides the failure
code.
--*/
{
// We do not instantiate any rpc interface in console winlogon any more
return TRUE;
}
BOOL
pHasPrefix (
IN PCWSTR Prefix,
IN PCWSTR String
)
/*++
Routine Description:
pHasPrefix checks String to see if it begins with Prefix. The check is
case-insensitive.
Arguments:
Prefix - Specifies the prefix to check
String - Specifies the string that may or may not have the prefix.
Return Value:
TRUE if String has the prefix, FALSE otherwise.
--*/
{
WCHAR c1 = 0, c2 = 0;
while (*Prefix && *String) {
c1 = (WCHAR) CharLower ((PWSTR) (*Prefix++));
c2 = (WCHAR) CharLower ((PWSTR) (*String++));
if (c1 != c2) {
return FALSE;
}
}
if (*Prefix) {
return FALSE; // String is smaller than Prefix
}
return TRUE;
}
PSID
pGetSidForUser (
IN PCWSTR Name
)
/*++
Routine Description:
pGetSidForUser is a wrapper to LookupAccountSid. It allocates the SID via
LocalAlloc.
Arguments:
Name - Specifies the user name to look up
Return Value:
A pointer to the SID, which must be freed with LocalFree, or NULL on error.
GetLastError provides failure code.
--*/
{
DWORD cbSid = 0;
PSID pSid = NULL;
DWORD cchDomain = 0;
PWSTR szDomain;
SID_NAME_USE SidUse;
BOOL bRet = FALSE;
bRet = LookupAccountName( NULL,
Name,
NULL,
&cbSid,
NULL,
&cchDomain,
&SidUse );
if (!bRet && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
pSid = (PSID) LocalAlloc(LPTR, cbSid);
if (!pSid) {
return NULL;
}
szDomain = (PWSTR) LocalAlloc(LPTR, cchDomain * sizeof(WCHAR));
if (!szDomain) {
LocalFree(pSid);
return NULL;
}
bRet = LookupAccountName( NULL,
Name,
pSid,
&cbSid,
szDomain,
&cchDomain,
&SidUse );
LocalFree(szDomain);
if (!bRet) {
LocalFree(pSid);
pSid = NULL;
}
}
return pSid;
}
BOOL
WINAPI
RemapAndMoveUserW (
IN PCWSTR RemoteTo,
IN DWORD Flags,
IN PCWSTR ExistingUser,
IN PCWSTR NewUser
)
/*++
Routine Description:
RemapAndMoveUserW is an API entry point to move settings of one SID to
another. In particular, it moves the local user profile, local group
memberships, some registry use of the SID, and some file system use of the
SID.
Arguments:
RemoteTo - Specifies the computer to remote the call to. Specify a
standard name ("\\computer" or "."). If NULL, the call will
be run locally.
Flags - Specifies zero, or REMAP_PROFILE_NOOVERWRITE.
ExistingUser - Specifies the existing user, in DOMAIN\user format. This
user must have a local profile.
NewUser - Specifies the new user who will own ExistingUser's profile
after completion. Specify the user in DOMAIN\User format.
Return Value:
TRUE on success, FALSE on failure, GetLastError provides the failure code.
--*/
{
DWORD Result = ERROR_SUCCESS;
HANDLE RpcHandle;
//
// We do not support profmap to run in remote computer until we move
// the ProfMapApi rpc interface to a LocalService hosted process
//
if (RemoteTo) {
DEBUGMSG((DM_WARNING, "RemapUserProfileW: received computer name"));
SetLastError (ERROR_INVALID_PARAMETER);
return FALSE;
}
__try {
if (pLocalRemapAndMoveUserW (Flags, ExistingUser, NewUser)) {
Result = ERROR_SUCCESS;
} else {
Result = GetLastError();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Result = ERROR_NOACCESS;
}
if (Result != ERROR_SUCCESS) {
SetLastError (Result);
return FALSE;
}
return TRUE;
}
BOOL
WINAPI
RemapAndMoveUserA (
IN PCSTR RemoteTo,
IN DWORD Flags,
IN PCSTR ExistingUser,
IN PCSTR NewUser
)
/*++
Routine Description:
RemapAndMoveUserA is the ANSI API entry point. It calls RemapAndMoveUserW.
Arguments:
RemoteTo - Specifies the computer to remote the call to. Specify a
standard name ("\\computer" or "."). If NULL, the call will
be run locally.
Flags - Specifies zero, or REMAP_PROFILE_NOOVERWRITE.
ExistingUser - Specifies the existing user, in DOMAIN\user format. This
user must have a local profile.
NewUser - Specifies the new user who will own ExistingUser's profile
after completion. Specify the user in DOMAIN\User format.
Return Value:
TRUE on success, FALSE on failure, GetLastError provides the failure code.
--*/
{
PWSTR UnicodeRemoteTo = NULL;
PWSTR UnicodeExistingUser = NULL;
PWSTR UnicodeNewUser = NULL;
DWORD Err;
BOOL b = TRUE;
Err = GetLastError();
__try {
if (RemoteTo) {
UnicodeRemoteTo = ProduceWFromA (RemoteTo);
if (!UnicodeRemoteTo) {
b = FALSE;
Err = GetLastError();
}
}
if (b) {
UnicodeExistingUser = ProduceWFromA (ExistingUser);
if (!UnicodeExistingUser) {
b = FALSE;
Err = GetLastError();
}
}
if (b) {
UnicodeNewUser = ProduceWFromA (NewUser);
if (!UnicodeNewUser) {
b = FALSE;
Err = GetLastError();
}
}
if (b) {
b = RemapAndMoveUserW (
UnicodeRemoteTo,
Flags,
UnicodeExistingUser,
UnicodeNewUser
);
if (!b) {
Err = GetLastError();
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Err = ERROR_NOACCESS;
}
SmartLocalFree (UnicodeRemoteTo);
SmartLocalFree (UnicodeExistingUser);
SmartLocalFree (UnicodeNewUser);
SetLastError (Err);
return b;
}
BOOL
pDoesUserHaveProfile (
IN PSID Sid
)
/*++
Routine Description:
pDoesUserHaveProfile checks if a profile exists for the user, who is
specified by the SID.
Arguments:
Sid - Specifies the SID of the user who may or may not have a local
profile
Return Value:
TRUE if the user has a profile and the profile directory exists, FALSE
otherwise.
--*/
{
WCHAR ProfileDir[MAX_PATH];
if (GetProfileRoot (Sid, ProfileDir, ARRAYSIZE(ProfileDir))) {
return TRUE;
}
return FALSE;
}
BOOL
pLocalRemapAndMoveUserW (
IN DWORD Flags,
IN PCWSTR ExistingUser,
IN PCWSTR NewUser
)
/*++
Routine Description:
pLocalRemapAndMoveUserW implements the remap and move of a user's security
settings. This includes moving the user's profile, moving local group
membership, adjusting some of the SIDs in the registry, and adjusting some
of the SID directories in the file system. Upon completion, NewUser will
own ExistingUser's profile.
Arguments:
Flags - Specifies the profile remap flags. See RemapUserProfile.
ExistingUser - Specifies the user who owns the local profile, in
DOMAIN\User format.
NewUser - Specifies the user who will own ExistingUser's profile upon
completion. Specify the user in DOMAIN\User format.
Return Value:
TRUE upon success, FALSE otherwise, GetLastError provides the failure code.
--*/
{
NET_API_STATUS Status;
DWORD Entries;
DWORD EntriesRead;
BOOL b = FALSE;
DWORD Error;
WCHAR Computer[MAX_PATH];
DWORD Size;
PSID ExistingSid = NULL;
PSID NewSid = NULL;
USER_INFO_0 ui0;
PLOCALGROUP_USERS_INFO_0 lui0 = NULL;
LOCALGROUP_MEMBERS_INFO_0 lmi0;
PCWSTR FixedExistingUser;
PCWSTR FixedNewUser;
BOOL NewUserIsOnDomain = FALSE;
BOOL ExistingUserIsOnDomain = FALSE;
HANDLE hToken = NULL;
BOOL NewUserProfileExists = FALSE;
HRESULT hr;
Error = GetLastError();
__try {
//
// Guard the API for admins only
//
if (!OpenThreadToken (GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken)) {
if (!OpenProcessToken (GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: OpenProcessToken failed with code %u", Error));
goto Exit;
}
}
if (!IsUserAnAdminMember (hToken)) {
Error = ERROR_ACCESS_DENIED;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Caller is not an administrator"));
goto Exit;
}
#ifdef DBG
{
PSID DbgSid;
PWSTR DbgSidString;
DbgSid = GetUserSid (hToken);
if (OurConvertSidToStringSid (DbgSid, &DbgSidString)) {
DEBUGMSG ((DM_VERBOSE, "pLocalRemapAndMoveUserW: Caller's SID is %s", DbgSidString));
DeleteSidString (DbgSidString);
}
DeleteUserSid (DbgSid);
}
#endif
//
// Determine if profile users are domain users or local users.
// Because the user names are in netbios format of domain\user,
// we know the user is local only when domain matches the
// computer name.
//
Size = ARRAYSIZE(Computer) - 2;
if (!GetComputerName (Computer, &Size)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: GetComputerName failed with code %u", Error));
goto Exit;
}
hr = StringCchCat(Computer, ARRAYSIZE(Computer), L"\\");
if (FAILED(hr)) {
Error = HRESULT_CODE(hr);
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: StringCchCat failed with code %u", Error));
goto Exit;
}
if (pHasPrefix (Computer, ExistingUser)) {
FixedExistingUser = ExistingUser + lstrlen (Computer);
} else {
FixedExistingUser = ExistingUser;
ExistingUserIsOnDomain = TRUE;
}
if (pHasPrefix (Computer, NewUser)) {
FixedNewUser = NewUser + lstrlen (Computer);
} else {
FixedNewUser = NewUser;
NewUserIsOnDomain = TRUE;
}
//
// Get the SID of the NewUser. This might fail.
//
NewSid = pGetSidForUser (NewUser);
if (!NewSid) {
Status = GetLastError();
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Can't get info for %s, rc=%u", NewUser, Status));
} else {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: NewUser exists"));
//
// Determine if new user has a profile
//
NewUserProfileExists = pDoesUserHaveProfile (NewSid);
if (NewUserProfileExists) {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: NewUser profile exists"));
}
}
if (NewUserProfileExists && (Flags & REMAP_PROFILE_NOOVERWRITE)) {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Can't overwrite existing user"));
Error = ERROR_USER_EXISTS;
goto Exit;
}
//
// Get the SID for ExitingUser. This cannot fail.
//
ExistingSid = pGetSidForUser (ExistingUser);
if (!ExistingSid) {
Error = ERROR_NONE_MAPPED;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: No SID for %s", ExistingUser));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Got SIDs for users"));
//
// Decide which of the four cases we are doing:
//
// Case 1: Local account to local account
// Case 2: Domain account to local account
// Case 3: Local account to domain account
// Case 4: Domain account to domain account
//
if (!NewUserIsOnDomain && !ExistingUserIsOnDomain) {
//
// For Case 1, all we do is rename the user, and we're done.
//
//
// To rename the user, it may be necessary to delete the
// existing "new" user. This abandons a profile.
//
if (NewSid) {
if (Flags & REMAP_PROFILE_NOOVERWRITE) {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Can't overwrite %s", FixedNewUser));
Error = ERROR_USER_EXISTS;
goto Exit;
}
Status = NetUserDel (NULL, FixedNewUser);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Can't remove %s, code %u", FixedNewUser, Status));
goto Exit;
}
}
//
// Now the NewUser does not exist. We can move ExistingUser
// to MoveUser via net apis. The SID doesn't change.
//
ui0.usri0_name = (PWSTR) FixedNewUser;
Status = NetUserSetInfo (
NULL,
FixedExistingUser,
0,
(PBYTE) &ui0,
NULL
);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((
DM_VERBOSE,
"pLocalRemapAndMoveUserW: Error renaming %s to %s, code %u",
FixedExistingUser,
FixedNewUser,
Status
));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Renamed local user %s to %s", FixedExistingUser, FixedNewUser));
b = TRUE;
goto Exit;
}
//
// For cases 2 through 4, we need to verify that the new user
// already exists, then we adjust the system security and fix
// SID use.
//
if (!NewSid) {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: User %s must exist", FixedNewUser));
Error = ERROR_NO_SUCH_USER;
goto Exit;
}
//
// Move the profile from ExistingUser to NewUser
//
if (!pLocalRemapUserProfileW (0, ExistingSid, NewSid)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: LocalRemapUserProfileW failed with code %u", Error));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Profile was remapped"));
//
// Put NewUser in all the same groups as ExistingUser
//
Status = NetUserGetLocalGroups (
NULL,
FixedExistingUser,
0,
0,
(PBYTE *) &lui0,
MAX_PREFERRED_LENGTH,
&EntriesRead,
&Entries
);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: NetUserGetLocalGroups failed with code %u for %s", Status, FixedExistingUser));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Local groups: %u", EntriesRead));
lmi0.lgrmi0_sid = NewSid;
for (Entries = 0 ; Entries < EntriesRead ; Entries++) {
Status = NetLocalGroupAddMembers (
NULL,
lui0[Entries].lgrui0_name,
0,
(PBYTE) &lmi0,
1
);
if (Status == ERROR_MEMBER_IN_ALIAS) {
Status = ERROR_SUCCESS;
}
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((
DM_VERBOSE,
"pLocalRemapAndMoveUserW: NetLocalGroupAddMembers failed with code %u for %s",
Status,
lui0[Entries].lgrui0_name
));
goto Exit;
}
}
NetApiBufferFree (lui0);
lui0 = NULL;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Local groups transferred"));
//
// Perform fixups
//
pFixSomeSidReferences (ExistingSid, NewSid);
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Some SID references fixed"));
//
// Remove ExistingUser
//
if (!ExistingUserIsOnDomain) {
//
// Local user case: delete the user account
//
if (Flags & REMAP_PROFILE_KEEPLOCALACCOUNT) {
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Keeping local account"));
} else {
Status = NetUserDel (NULL, FixedExistingUser);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: NetUserDel failed with code %u for %s", Error, FixedExistingUser));
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Ignoring error because changes cannot be undone!"));
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Removed local user %s", FixedExistingUser));
}
} else {
//
// Non-local user case: remove the user from each local group
//
Status = NetUserGetLocalGroups (
NULL,
FixedExistingUser,
0,
0,
(PBYTE *) &lui0,
MAX_PREFERRED_LENGTH,
&EntriesRead,
&Entries
);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((
DM_VERBOSE,
"pLocalRemapAndMoveUserW: NetUserGetLocalGroups failed with code %u for %s",
Error,
FixedExistingUser
));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Got local groups for %s", FixedExistingUser));
lmi0.lgrmi0_sid = ExistingSid;
for (Entries = 0 ; Entries < EntriesRead ; Entries++) {
Status = NetLocalGroupDelMembers (
NULL,
lui0[Entries].lgrui0_name,
0,
(PBYTE) &lmi0,
1
);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((
DM_VERBOSE,
"pLocalRemapAndMoveUserW: NetLocalGroupDelMembers failed with code %u for %s",
Error,
lui0[Entries].lgrui0_name
));
goto Exit;
}
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Removed local group membership"));
}
DEBUGMSG((DM_VERBOSE, "pLocalRemapAndMoveUserW: Success"));
b = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Error = ERROR_NOACCESS;
}
Exit:
if (hToken) {
CloseHandle (hToken);
}
if (lui0) {
NetApiBufferFree (lui0);
}
SmartLocalFree (ExistingSid);
SmartLocalFree (NewSid);
SetLastError (Error);
return b;
}
typedef struct {
PCWSTR Path;
WCHAR ExpandedPath[MAX_PATH];
} IGNOREPATH, *PIGNOREPATH;
BOOL
IsPatternMatchW (
IN PCWSTR Pattern,
IN PCWSTR Str
)
{
WCHAR chSrc, chPat;
while (*Str) {
chSrc = (WCHAR) CharLowerW ((PWSTR) *Str);
chPat = (WCHAR) CharLowerW ((PWSTR) *Pattern);
if (chPat == L'*') {
// Skip all asterisks that are grouped together
while (Pattern[1] == L'*') {
Pattern++;
}
// Check if asterisk is at the end. If so, we have a match already.
chPat = (WCHAR) CharLowerW ((PWSTR) Pattern[1]);
if (!chPat) {
return TRUE;
}
// Otherwise check if next pattern char matches current char
if (chPat == chSrc || chPat == L'?') {
// do recursive check for rest of pattern
Pattern++;
if (IsPatternMatchW (Pattern, Str)) {
return TRUE;
}
// no, that didn't work, stick with star
Pattern--;
}
//
// Allow any character and continue
//
Str++;
continue;
}
if (chPat != L'?') {
//
// if next pattern character is not a question mark, src and pat
// must be identical.
//
if (chSrc != chPat) {
return FALSE;
}
}
//
// Advance when pattern character matches string character
//
Pattern++;
Str++;
}
//
// Fail when there is more pattern and pattern does not end in an asterisk
//
chPat = *Pattern;
if (chPat && (chPat != L'*' || Pattern[1])) {
return FALSE;
}
return TRUE;
}
void FindAndReplaceSD(
IN PSECURITY_DESCRIPTOR pSD,
OUT PSECURITY_DESCRIPTOR* ppNewSD,
IN PCWSTR ExistingSidString,
IN PCWSTR NewSidString)
/*++
Routine Description:
FindAndReplaceSD replaces ExistingSidString with NewSidString from
string representation of pSD and return the newly constructed SD
Arguments:
pSD - Security Descriptor to replace
pNewSD - New security descriptor place holder
ExistingSidString - Specifies the string version of the SID to find.
NewSidString - Specifies the SID to replace in ACE when
ExistingSidString is found.
Return Value:
None.
--*/
{
LPWSTR szStringSD;
LPWSTR szNewStringSD;
if (ppNewSD) {
*ppNewSD = pSD;
}
if (!ConvertSecurityDescriptorToStringSecurityDescriptor( pSD,
SDDL_REVISION_1,
DACL_SECURITY_INFORMATION,
&szStringSD,
NULL )) {
DEBUGMSG((DM_VERBOSE, "FindAndReplaceSD: No string found. Error %d", GetLastError()));
return;
}
szNewStringSD = StringSearchAndReplaceW(szStringSD, ExistingSidString, NewSidString, NULL);
if (szNewStringSD) {
if (!ConvertStringSecurityDescriptorToSecurityDescriptor( szNewStringSD,
SDDL_REVISION_1,
ppNewSD,
NULL )) {
DEBUGMSG((DM_VERBOSE, "FindAndReplaceSD: Fail to convert string to SD. Error %d", GetLastError()));
if (ppNewSD) {
*ppNewSD = pSD;
}
}
LocalFree(szNewStringSD);
}
LocalFree(szStringSD);
}
VOID
pFixDirReference (
IN PCWSTR CurrentPath,
IN PCWSTR ExistingSidString,
IN PCWSTR NewSidString,
IN PIGNOREPATH IgnoreDirList OPTIONAL
)
/*++
Routine Description:
pFixDirReference is a recursive function that renames a directory if it
matches an existing SID exactly. It also updates the SIDs.
Arguments:
CurrentPath - Specifies the full file system path.
ExistingSidString - Specifies the string version of the SID to find.
NewSidString - Specifies the SID to rename the directory to when
ExistingSidString is found.
IgnoreDirList - Specifies a list of directories to ignore. A NULL
Path member indicates the end of the list, and the
ExpandedPath member must be filled by the caller.
Return Value:
None.
--*/
{
WIN32_FIND_DATA fd;
HANDLE hFind;
WCHAR SubPath[MAX_PATH];
WCHAR NewPath[MAX_PATH];
BOOL bIgnoreDir;
UINT u;
UINT cbNeededLen;
PSECURITY_DESCRIPTOR pSecurityDesc = NULL;
PSECURITY_DESCRIPTOR pNewSecurityDesc = NULL;
HRESULT hr;
if ((lstrlenW (CurrentPath) + lstrlenW (ExistingSidString) + 2) >= MAX_PATH) {
return;
}
if (*CurrentPath == 0) {
return;
}
hr = StringCchPrintf(SubPath, ARRAYSIZE(SubPath), L"%s\\*", CurrentPath);
if (FAILED(hr)) {
return;
}
hFind = FindFirstFile (SubPath, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
//
// Ignore dot and dot-dot
//
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!lstrcmpi (fd.cFileName, L".") || !lstrcmpi (fd.cFileName, L"..")) {
continue;
}
}
//
// Rename file/directory or recurse on directory
//
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
hr = StringCchPrintf(SubPath, ARRAYSIZE(SubPath), L"%s\\%s", CurrentPath, fd.cFileName);
if (FAILED(hr)) {
continue;
}
bIgnoreDir = FALSE;
if (IgnoreDirList) {
//
// Check if this path is to be ignored
//
for (u = 0 ; IgnoreDirList[u].Path ; u++) {
if (IgnoreDirList[u].ExpandedPath[0]) {
if (IsPatternMatchW (IgnoreDirList[u].ExpandedPath, SubPath)) {
bIgnoreDir = TRUE;
}
}
}
}
//
// If this path is not to be ignored, recursively fix it
//
if (!bIgnoreDir) {
//
// Check for reparse point
//
if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
DEBUGMSG((DM_WARNING, "pFixDirReference: Found a reparse point <%s>, will not recurse into it!", SubPath));
}
else // Recurse into it
{
pFixDirReference (SubPath,
ExistingSidString,
NewSidString,
IgnoreDirList);
}
} else {
//
// This path is to be ignored
//
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Ignoring path %s", SubPath));
continue;
}
}
if (!lstrcmpi (fd.cFileName, ExistingSidString)) {
//
// Rename the SID referenced in the file system
//
hr = StringCchPrintf(SubPath, ARRAYSIZE(SubPath), L"%s\\%s", CurrentPath, ExistingSidString);
if (FAILED(hr)) {
continue;
}
hr = StringCchPrintf(NewPath, ARRAYSIZE(NewPath), L"%s\\%s", CurrentPath, NewSidString);
if (FAILED(hr)) {
continue;
}
if (MoveFile (SubPath, NewPath)) {
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Moved %s to %s", SubPath, NewPath));
}
else {
DEBUGMSG((DM_WARNING, "pFixDirReference: Faile to move %s to %s. Error %d", SubPath, NewPath, GetLastError()));
}
//
// Obtain security descriptor of the file/dir and replace it if necessary
//
if (!GetFileSecurity (NewPath,
DACL_SECURITY_INFORMATION,
NULL,
0,
&cbNeededLen )) {
pSecurityDesc = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbNeededLen);
if (!pSecurityDesc) {
continue;
}
if (!GetFileSecurity (NewPath,
DACL_SECURITY_INFORMATION,
pSecurityDesc,
cbNeededLen,
&cbNeededLen )) {
DEBUGMSG((DM_WARNING, "pFixDirReference: Path %s has no security descriptor", SubPath));
LocalFree(pSecurityDesc);
continue;
}
FindAndReplaceSD(pSecurityDesc, &pNewSecurityDesc, ExistingSidString, NewSidString);
if (!SetFileSecurity (NewPath,
DACL_SECURITY_INFORMATION,
pNewSecurityDesc) ) {
DEBUGMSG((DM_WARNING, "pFixDirReference: Fail to update security for %s. Error %d", SubPath, GetLastError()));
}
else {
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Updating security for %s", SubPath));
}
if (pNewSecurityDesc && pSecurityDesc != pNewSecurityDesc) {
LocalFree(pNewSecurityDesc);
pNewSecurityDesc = NULL;
}
if (pSecurityDesc) {
LocalFree(pSecurityDesc);
pSecurityDesc = NULL;
}
} else {
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Path %s has no security descriptor", SubPath));
}
}
} while (FindNextFile (hFind, &fd));
FindClose (hFind);
}
}
BOOL
pOurExpandEnvironmentStrings (
IN PCWSTR String,
OUT PWSTR OutBuffer,
IN UINT cchBuffer,
IN PCWSTR UserProfile, OPTIONAL
IN HKEY UserHive OPTIONAL
)
/*++
Routine Description:
pOurExpandEnvironmentStrings expands standard environment variables,
implementing special cases for the variables that have different values
than what the profmap.dll environment has. In particular, %APPDATA% and
%USERPROFILE% are obtained by quering the registry.
Because this routine is private, certain assumptions are made, such as
the %APPDATA% or %USERPROFILE% environment variables must appear only
at the begining of String.
Arguments:
String - Specifies the string that might contain one or more
environment variables.
OutBuffer - Receivies the expanded string
cchBuffer - Buffer size
UserProfile - Specifies the root to the user's profile
UserHive - Specifies the handle of the root to the user's registry hive
Return Value:
TRUE if the string was expanded, or FALSE if it is longer than MAX_PATH.
OutBuffer is always valid upon return. Note that it might be an empty
string.
--*/
{
WCHAR TempBuf1[MAX_PATH*2];
WCHAR TempBuf2[MAX_PATH*2];
PCWSTR CurrentString;
DWORD Size;
HKEY Key;
LONG rc;
HRESULT hr;
CurrentString = String;
//
// Special case -- replace %APPDATA% with the app data from the user hive
//
if (UserHive && pHasPrefix(L"%APPDATA%", CurrentString)) {
rc = RegOpenKeyEx (
UserHive,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
0,
KEY_READ,
&Key
);
if (rc == ERROR_SUCCESS) {
Size = MAX_PATH - lstrlen (CurrentString + 1);
rc = RegQueryValueEx (
Key,
L"AppData",
NULL,
NULL,
(PBYTE) TempBuf1,
&Size
);
RegCloseKey (Key);
}
if (rc != ERROR_SUCCESS) {
//
// In case of an error, use a wildcard
//
hr = StringCchCopy(TempBuf1, ARRAYSIZE(TempBuf1), UserProfile);
if (FAILED(hr)) {
goto Exit;
}
hr = StringCchCat(TempBuf1, ARRAYSIZE(TempBuf1), L"\\*");
if (FAILED(hr)) {
goto Exit;
}
} else {
DEBUGMSG ((DM_VERBOSE, "Got AppData path from user hive: %s", TempBuf1));
}
hr = StringCchCat(TempBuf1, ARRAYSIZE(TempBuf1), CurrentString + 9);
if (FAILED(hr)) {
goto Exit;
}
CurrentString = TempBuf1;
}
//
// Special case -- replace %USERPROFILE% with ProfileRoot, because
// our environment is for another user
//
if (UserProfile && pHasPrefix(L"%USERPROFILE%", CurrentString)) {
hr = StringCchCopy(TempBuf2, ARRAYSIZE(TempBuf2), UserProfile);
if (FAILED(hr)) {
goto Exit;
}
hr = StringCchCat(TempBuf2, ARRAYSIZE(TempBuf2), CurrentString + 13);
if (FAILED(hr)) {
goto Exit;
}
CurrentString = TempBuf2;
}
//
// Now replace other environment variables
//
Size = ExpandEnvironmentStrings (CurrentString, OutBuffer, cchBuffer);
if (Size && Size <= cchBuffer) {
return TRUE;
}
Exit:
*OutBuffer = 0;
return FALSE;
}
typedef struct {
HKEY hRoot;
PCWSTR szKey;
} REGPATH, *PREGPATH;
VOID
pFixSomeSidReferences (
PSID ExistingSid,
PSID NewSid
)
/*++
Routine Description:
pFixSomeSidReferences adjusts important parts of the system that use SIDs.
When a SID changes, this function adjusts the system, so the new SID is
used and no settings are lost. This function adjusts the registry and file
system. It does not attempt to adjust SID use whereever a SID might be
used.
For Win2k, this code deliberately ignores crypto sid directories, because
the original SID is used as part of the recovery encryption key. In future
versions, proper migration of these settings is expected.
This routine also blows away the ProtectedRoots subkey for the crypto APIs.
The ProtectedRoots key has an ACL, and when we delete the key, the cyrpto
APIs will rebuild it with the proper ACL.
WARNING: We know there is a risk in loss of access to data that was encrypted
using the SID. Normally the original account will not be removed,
so the SID will exist on the system, and that (in theory) allows the
original data to be recovered. But because the cyrpto code gets the
SID from the file system, there is no way for the user to decrypt
their data. The future crypto migration code should fix this issue.
Arguments:
ExistingSid - Specifies the SID that potentially has settings somewhere on
the system.
NewSid - Specifies the SID that is replacing ExistingSid.
Return Value:
None.
--*/
{
PWSTR ExistingSidString = NULL;
PWSTR NewSidString = NULL;
UINT u;
WCHAR ExpandedRoot[MAX_PATH];
WCHAR ProfileRoot[MAX_PATH];
HKEY UserHive = NULL;
WCHAR HivePath[MAX_PATH + 14];
LONG rc;
HRESULT hr;
REGPATH RegRoots[] = {
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Protected Storage System Provider" },
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\EventSystem" },
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Installer\\Managed" },
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" },
{ NULL, NULL }
};
PCWSTR DirList[] = {
L"%SYSTEMROOT%\\system32\\appmgmt",
NULL
};
IGNOREPATH IgnoreDirList[] = {
{L"%APPDATA%\\Microsoft\\Crypto", L""},
{L"%APPDATA%\\Microsoft\\Protect", L""},
{NULL, L""}
};
//
// Get the SIDs in text format
//
if (!OurConvertSidToStringSid (ExistingSid, &ExistingSidString)) {
goto Exit;
}
if (!OurConvertSidToStringSid (NewSid, &NewSidString)) {
goto Exit;
}
//
// Initialize directory strings and load the user hive
//
if (!GetProfileRoot(NewSid, ProfileRoot, ARRAYSIZE(ProfileRoot))) {
goto Exit;
}
DEBUGMSG ((DM_VERBOSE, "ProfileRoot (NewSid): %s", ProfileRoot));
hr = StringCchPrintf(HivePath, ARRAYSIZE(HivePath), L"%s\\ntuser.dat", ProfileRoot);
if (FAILED(hr)) {
goto Exit;
}
DEBUGMSG ((DM_VERBOSE, "User hive: %s", HivePath));
rc = RegLoadKey (HKEY_LOCAL_MACHINE, REMAP_KEY_NAME, HivePath);
if (rc == ERROR_SUCCESS) {
rc = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
REMAP_KEY_NAME,
0,
KEY_READ|KEY_WRITE,
&UserHive
);
if (rc != ERROR_SUCCESS) {
RegUnLoadKey (HKEY_LOCAL_MACHINE, REMAP_KEY_NAME);
DEBUGMSG ((DM_WARNING, "pFixSomeSidReferences: Can't open user hive root, rc=%u", rc));
UserHive = NULL;
goto Exit;
}
} else {
DEBUGMSG ((DM_WARNING, "RemapAndMoveUserW: Can't load user's hive, rc=%u", rc));
goto Exit;
}
for (u = 0 ; IgnoreDirList[u].Path ; u++) {
pOurExpandEnvironmentStrings (
IgnoreDirList[u].Path,
IgnoreDirList[u].ExpandedPath,
ARRAYSIZE(IgnoreDirList[u].ExpandedPath),
ProfileRoot,
UserHive
);
DEBUGMSG((DM_VERBOSE, "pFixSomeSidReferences: Ignoring %s", IgnoreDirList[u].ExpandedPath));
}
//
// Search and replace select parts of the registry where SIDs are used
//
for (u = 0 ; RegRoots[u].hRoot ; u++) {
RegistrySearchAndReplaceW( RegRoots[u].hRoot,
RegRoots[u].szKey,
ExistingSidString,
NewSidString );
}
//
// Test for directories and rename them
//
for (u = 0 ; DirList[u] ; u++) {
if (pOurExpandEnvironmentStrings (DirList[u], ExpandedRoot, ARRAYSIZE(ExpandedRoot), ProfileRoot, UserHive)) {
pFixDirReference (ExpandedRoot,
ExistingSidString,
NewSidString,
IgnoreDirList );
}
}
//
// Fix profile directory
//
pFixDirReference (ProfileRoot,
ExistingSidString,
NewSidString,
IgnoreDirList );
//
// Crypto special case -- blow away ProtectedRoots key (413828)
//
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key, code is currently disabled"));
if (UserHive) {
rc = RegDelnode (UserHive, L"Software\\Microsoft\\SystemCertificates\\Root\\ProtectedRoots");
if (rc != ERROR_SUCCESS) {
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key, GLE=%u", rc));
}
} else {
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key because user hive could not be opened"));
}
Exit:
//
// Cleanup
//
if (UserHive) {
RegCloseKey (UserHive);
RegUnLoadKey (HKEY_LOCAL_MACHINE, REMAP_KEY_NAME);
}
if (ExistingSidString) {
DeleteSidString (ExistingSidString);
}
if (NewSidString) {
DeleteSidString (NewSidString);
}
}