windows-nt/Source/XPSP1/NT/ds/security/gina/profmap/dll/profmap.c

2839 lines
69 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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"
//
// Worker prototypes
//
DWORD
pRemapUserProfile (
IN DWORD Flags,
IN PSID SidCurrent,
IN PSID SidNew
);
HANDLE
pConnectToServer(
IN PCWSTR ServerName
);
VOID
pDisconnectFromServer (
IN HANDLE RpcHandle
);
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
)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: OpenThreadToken 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 (TRUE) {
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);
SmartLocalFree (DomainBuffer);
SetLastError (Result);
return FALSE;
}
*User = UserBuffer;
*Domain = DomainBuffer;
return TRUE;
}
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 Size2;
DWORD Result = ERROR_SUCCESS;
INT UserCompare;
INT DomainCompare;
BOOL b;
HKEY hCurrentProfile = NULL;
HKEY hNewProfile = NULL;
HKEY hProfileList = NULL;
LONG rc;
DWORD Disposition;
DWORD Index;
PWSTR pValue = NULL;
DWORD ValueSize;
PBYTE pData = NULL;
DWORD DataSize;
DWORD Type;
BOOL CleanUpFailedCopy = FALSE;
WCHAR ProfileDir[MAX_PATH];
DWORD Loaded;
//
// 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;
}
//
// Prepare transfer memory
//
ValueSize = 1024;
pValue = (PWSTR) LocalAlloc (LPTR, ValueSize);
if (!pValue) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Value alloc error %d", GetLastError()));
goto Exit;
}
DataSize = 4096;
pData = (PBYTE) LocalAlloc (LPTR, DataSize);
if (!pData) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Data alloc error %d", GetLastError()));
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, &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;
}
Size = sizeof(ProfileDir);
rc = RegQueryValueEx (hCurrentProfile, PROFILE_IMAGE_VALUE_NAME, NULL,
&Type, (LPBYTE) ProfileDir, &Size);
if (rc != ERROR_SUCCESS || (Type != REG_SZ && Type != REG_EXPAND_SZ)) {
Result = ERROR_BAD_USER_PROFILE;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Current user does not have a profile path."));
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 = RegCreateKeyEx(hProfileList, NewSidString, 0, 0, 0,
KEY_READ | KEY_WRITE, NULL, &hNewProfile, &Disposition);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't create destination profile entry."));
goto Exit;
}
if (Disposition == REG_OPENED_EXISTING_KEY) {
//
// 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;
if (!RegDelnode (hProfileList, NewSidString)) {
Result = GetLastError();
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't reset new profile list key."));
goto Exit;
}
//
// Reopen the destination key
//
rc = RegCreateKeyEx(hProfileList, NewSidString, 0, 0, 0,
KEY_READ | KEY_WRITE, NULL, &hNewProfile, &Disposition);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't create new profile list key after successful delete."));
goto Exit;
}
}
//
// Transfer contents of current user key to new user. Unfortunately,
// because the registry APIs don't have a rename capability, we can't
// be fool-proof here. If we fail to transfer one or more settings,
// the profile can wind up broken.
//
// If an error is encountered, we abandon the successful work above,
// which includes possibly deletion of an existing profile list key.
//
CleanUpFailedCopy = TRUE;
for (Index = 0 ; ; Index++) {
Size = ValueSize;
Size2 = DataSize;
rc = RegEnumValue (hCurrentProfile, Index, pValue, &Size, NULL,
&Type, pData, &Size2);
if (rc == ERROR_NO_MORE_ITEMS) {
break;
}
if (rc == ERROR_MORE_DATA) {
//
// Grow buffer(s) and try again
//
ASSERT (Size > ValueSize || Size2 > DataSize);
if (Size > ValueSize) {
LocalFree (pValue);
pValue = (PWSTR) LocalAlloc (LPTR, Size);
if (!pValue) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't alloc %u bytes for value name.", Size));
goto Exit;
}
}
if (Size2 > DataSize) {
LocalFree (pData);
pData = (PBYTE) LocalAlloc (LPTR, Size2);
if (!pData) {
Result = ERROR_OUTOFMEMORY;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Can't alloc %u bytes for value data.", Size2));
goto Exit;
}
}
rc = RegEnumValue (hCurrentProfile, Index, pValue, &Size, NULL,
&Type, pData, &Size2);
}
ASSERT (rc != ERROR_MORE_DATA);
if (rc == ERROR_SUCCESS) {
//
// We have the value, now save it.
//
rc = RegSetValueEx (hNewProfile, pValue, 0, Type, pData, Size2);
if (rc != ERROR_SUCCESS) {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: RegSetValueEx returned %d.", rc));
goto Exit;
}
} else {
Result = rc;
DEBUGMSG((DM_WARNING, "pRemapUserProfile: RegEnumValueEx returned %d.", rc));
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 if it exists. It will get re-established on the next logon.
//
RegDeleteValue (hNewProfile, L"GUID");
//
// 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;
}
//
// Remove current user profile list key. If removal fails, the API
// will not fail.
//
CleanUpFailedCopy = FALSE;
RegCloseKey (hCurrentProfile);
hCurrentProfile = NULL;
if (!DeleteProfileRegistrySettings (CurrentSidString)) {
DEBUGMSG((DM_WARNING, "pRemapUserProfile: Delete of original profile failed with code %d. Ignoring.", GetLastError()));
}
//
// Success -- the profile was transferred and nothing went wrong
//
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);
SmartLocalFree (pValue);
SmartLocalFree (pData);
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;
}
if (!Computer) {
Computer = L".";
}
__try {
RpcHandle = pConnectToServer (Computer);
if (!RpcHandle) {
Result = GetLastError();
}
}
__except (TRUE) {
Result = ERROR_NOACCESS;
}
if (Result != ERROR_SUCCESS) {
SetLastError (Result);
return FALSE;
}
Result = ProfMapCli_RemoteRemapUserProfile (
RpcHandle,
Flags,
SidCurrent,
GetLengthSid (SidCurrent),
SidNew,
GetLengthSid (SidNew)
);
pDisconnectFromServer (RpcHandle);
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;
}
DWORD
WINAPI
ProfMapSrv_RemoteRemapUserProfile (
IN HANDLE RpcHandle,
IN DWORD Flags,
IN PBYTE CurrentSid,
IN DWORD CurrentSidSize,
IN PBYTE NewSid,
IN DWORD NewSidSize
)
/*++
Routine Description:
ProfMapSrv_RemoteRemapUserProfile implements the server-side API. This
function is called when a client makes a RPC request.
Arguments:
RpcHandle - The binding handle, provided by the MIDL stub code.
Flags - Specifies the profile mapping flags.
CurrentSid - Specifies the SID of the user who owns the profile.
CurrentSidSize - Specifies the size, in bytes, of CurrentSid.
NewSid - Specifies the SID of the user who will own the profile
after completion.
NewSidSize - Specifies the size, in bytes, of NewSid.
Return Value:
A Win32 status code. This value is passed via the MIDL stub back to the
RPC client.
--*/
{
DWORD Result;
RPC_STATUS RpcStatus;
RpcStatus = RpcImpersonateClient (NULL);
if (RpcStatus != ERROR_SUCCESS) {
DEBUGMSG((DM_WARNING, "Call request denied by RPC impersonation.", RpcHandle));
return RpcStatus;
}
if (pLocalRemapUserProfileW (Flags, (PSID) CurrentSid, (PSID) NewSid)) {
Result = ERROR_SUCCESS;
} else {
Result = GetLastError();
}
RpcRevertToSelf();
DEBUGMSG((DM_VERBOSE, "RPC request completed with code %u", Result));
return Result;
}
HANDLE
pConnectToServer(
IN PCWSTR ServerName
)
/*++
Routine Description:
pConnectToServer connects a client to a server.
Arguments:
ServerName - Specifies the server to connect to. The server name is a
standard name ("\\computer" or ".").
Return Value:
A handle to the server connection, or NULL if the server could not be
reached. GetLastError provides the failure code.
--*/
{
RPC_BINDING_HANDLE RpcHandle;
NTSTATUS Status;
Status = RpcpBindRpc (
(PWSTR) ServerName,
L"ProfMapApi",
0,
&RpcHandle
);
if (!NT_SUCCESS(Status)) {
SetLastError (Status);
return NULL;
}
return RpcHandle;
}
VOID
pDisconnectFromServer (
IN HANDLE RpcHandle
)
/*++
Routine Description:
pDisconnectFromServer closes the server binding handle opened by
pConnectToServer.
Arguments:
RpcHandle - Specifies a non-NULL binding handle, as returned from
pConnectToServer.
Return Value:
None.
--*/
{
RpcpUnbindRpc( RpcHandle );
}
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.
--*/
{
NTSTATUS Status;
Status = RpcpInitRpcServer();
if (!NT_SUCCESS(Status)) {
SetLastError (Status);
return FALSE;
}
Status = RpcpStartRpcServer( L"ProfMapApi", ProfMapSrv_pmapapi_ServerIfHandle );
if (!NT_SUCCESS(Status)) {
SetLastError (Status);
return FALSE;
}
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) {
c1 = (WCHAR) CharLower ((PWSTR) (*Prefix++));
c2 = (WCHAR) CharLower ((PWSTR) (*String++));
if (c1 != c2) {
break;
}
}
return c1 == c2;
}
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 Size;
PSID Buffer;
DWORD DomainSize;
PWSTR Domain;
SID_NAME_USE Use;
BOOL b = FALSE;
Size = 256;
Buffer = (PSID) LocalAlloc (LPTR, Size);
if (!Buffer) {
return NULL;
}
DomainSize = 256;
Domain = (PWSTR) LocalAlloc (LPTR, DomainSize);
if (!Domain) {
LocalFree (Buffer);
return NULL;
}
b = LookupAccountName (
NULL,
Name,
Buffer,
&Size,
Domain,
&DomainSize,
&Use
);
if (Size > 256) {
LocalFree (Buffer);
Buffer = (PSID) LocalAlloc (LPTR, Size);
if (!Buffer) {
LocalFree (Domain);
return NULL;
}
}
if (DomainSize > 256) {
LocalFree (Domain);
Domain = (PWSTR) LocalAlloc (LPTR, DomainSize);
if (!Domain) {
LocalFree (Buffer);
return NULL;
}
}
if (Size > 256 || DomainSize > 256) {
b = LookupAccountName (
NULL,
Name,
Buffer,
&Size,
Domain,
&DomainSize,
&Use
);
}
LocalFree (Domain);
if (!b) {
LocalFree (Buffer);
return NULL;
}
return Buffer;
}
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;
if (!RemoteTo) {
RemoteTo = L".";
}
__try {
RpcHandle = pConnectToServer (RemoteTo);
if (!RpcHandle) {
Result = GetLastError();
}
}
__except (TRUE) {
Result = ERROR_NOACCESS;
}
if (Result != ERROR_SUCCESS) {
SetLastError (Result);
return FALSE;
}
__try {
Result = ProfMapCli_RemoteRemapAndMoveUser (
RpcHandle,
Flags,
(PWSTR) ExistingUser,
(PWSTR) NewUser
);
}
__except (TRUE) {
Result = ERROR_NOACCESS;
}
pDisconnectFromServer (RpcHandle);
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;
__try {
Err = GetLastError();
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();
}
}
SmartLocalFree (UnicodeRemoteTo);
SmartLocalFree (UnicodeExistingUser);
SmartLocalFree (UnicodeNewUser);
}
__except (TRUE) {
SetLastError (ERROR_NOACCESS);
}
SetLastError (Err);
return b;
}
DWORD
WINAPI
ProfMapSrv_RemoteRemapAndMoveUser (
IN HANDLE RpcHandle,
IN DWORD Flags,
IN PWSTR ExistingUser,
IN PWSTR NewUser
)
/*++
Routine Description:
ProfMapSrv_RemoteRemapAndMoveUser implements the server function that is
called whenever a client makes an RPC request. This function calls the
local worker.
Arguments:
RpcHandle - A binding handle provided by the MIDL stub code.
Flags - Specifies the profile mapping flags.
ExistingUser - Specifies the user who owns the profile.
NewUser - Specifies the user who will take ownership of ExistingUser's
profile.
Return Value:
A Win32 status code, which is passed back to the client via RPC.
--*/
{
DWORD Result;
RPC_STATUS RpcStatus;
RpcStatus = RpcImpersonateClient (NULL);
if (RpcStatus != ERROR_SUCCESS) {
DEBUGMSG((DM_WARNING, "Call request denied by RPC impersonation.", RpcHandle));
return RpcStatus;
}
if (pLocalRemapAndMoveUserW (Flags, ExistingUser, NewUser)) {
Result = ERROR_SUCCESS;
} else {
Result = GetLastError();
}
RpcRevertToSelf();
DEBUGMSG((DM_VERBOSE, "RPC request completed with code %u", Result));
return Result;
}
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)) {
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;
Error = GetLastError();
__try {
//
// Guard the API for admins only
//
if (!OpenThreadToken (
GetCurrentThread(),
TOKEN_ALL_ACCESS,
FALSE,
&hToken
)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: OpenThreadToken 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
//
// 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 = MAX_PATH - 2;
if (!GetComputerName (Computer, &Size)) {
Error = GetLastError();
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: GetComputerName failed with code %u", Error));
goto Exit;
}
lstrcat (Computer, L"\\");
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, "RemapAndMoveUserW: Can't get info for %s, rc=%u", NewUser, Status));
} else {
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: NewUser exists"));
//
// Determine if new user has a profile
//
NewUserProfileExists = pDoesUserHaveProfile (NewSid);
if (NewUserProfileExists) {
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: NewUser profile exists"));
}
}
if (NewUserProfileExists && (Flags & REMAP_PROFILE_NOOVERWRITE)) {
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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, "RemapAndMoveUserW: No SID for %s", ExistingUser));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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, "RemapAndMoveUserW: Can't overwrite %s", FixedNewUser));
Error = ERROR_USER_EXISTS;
goto Exit;
}
Status = NetUserDel (NULL, FixedNewUser);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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,
"RemapAndMoveUserW: Error renaming %s to %s, code %u",
FixedExistingUser,
FixedNewUser,
Status
));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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, "RemapAndMoveUserW: 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, "RemapAndMoveUserW: LocalRemapUserProfileW failed with code %u", Error));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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, "RemapAndMoveUserW: NetUserGetLocalGroups failed with code %u for %s", Status, FixedExistingUser));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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,
"RemapAndMoveUserW: NetLocalGroupAddMembers failed with code %u for %s",
Status,
lui0[Entries].lgrui0_name
));
goto Exit;
}
}
NetApiBufferFree (lui0);
lui0 = NULL;
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Local groups transferred"));
//
// Perform fixups
//
pFixSomeSidReferences (ExistingSid, NewSid);
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Some SID references fixed"));
//
// Remove ExistingUser
//
if (!ExistingUserIsOnDomain) {
//
// Local user case: delete the user account
//
if (Flags & REMAP_PROFILE_KEEPLOCALACCOUNT) {
DEBUGMSG((DM_VERBOSE, "RemapUserProfile: Keeping local account"));
} else {
Status = NetUserDel (NULL, FixedExistingUser);
if (Status != ERROR_SUCCESS) {
Error = Status;
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: NetUserDel failed with code %u for %s", Error, FixedExistingUser));
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Ignoring error because changes cannot be undone!"));
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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,
"RemapAndMoveUserW: NetUserGetLocalGroups failed with code %u for %s",
Error,
FixedExistingUser
));
goto Exit;
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: 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,
"RemapAndMoveUserW: NetLocalGroupDelMembers failed with code %u for %s",
Error,
lui0[Entries].lgrui0_name
));
goto Exit;
}
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Removed local group membership"));
}
DEBUGMSG((DM_VERBOSE, "RemapAndMoveUserW: Success"));
b = TRUE;
}
__except (TRUE) {
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;
}
BOOL
DoesStringHavePrefixW (
IN PCWSTR Prefix,
IN PCWSTR String
)
{
while (*Prefix) {
if (CharLowerW ((PWSTR) *Prefix) != CharLowerW ((PWSTR) *String)) {
return FALSE;
}
Prefix++;
String++;
}
return TRUE;
}
VOID
pFixDirReference (
IN PCWSTR CurrentPath,
IN PCWSTR ExistingSidString,
IN PCWSTR NewSidString,
IN PACL NewAcl, OPTIONAL
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.
NewAcl - Specifies the new ACL to use
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];
GROWBUFFER Buf = GROWBUF_INIT;
UINT NeededLen;
BOOL Present;
BOOL Defaulted;
PACL Acl;
UINT u;
if ((lstrlenW (CurrentPath) + lstrlenW (ExistingSidString) + 2) >= MAX_PATH) {
return;
}
if (*CurrentPath == 0) {
return;
}
wsprintf (SubPath, L"%s\\*", CurrentPath);
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;
}
}
if (NewAcl) {
//
// Obtain security descriptor of the file
//
wsprintf (SubPath, L"%s\\%s", CurrentPath, fd.cFileName);
Buf.End = 0;
if (!GetFileSecurity (
SubPath,
DACL_SECURITY_INFORMATION,
Buf.Buf,
Buf.Size,
&NeededLen
)) {
if (Buf.Size < NeededLen) {
GrowBuffer (&Buf, NeededLen);
if (GetFileSecurity (
SubPath,
DACL_SECURITY_INFORMATION,
Buf.Buf,
Buf.Size,
&NeededLen
)) {
Buf.End = GetSecurityDescriptorLength (
(PSECURITY_DESCRIPTOR) Buf.Buf
);
}
}
} else {
Buf.End = GetSecurityDescriptorLength (
(PSECURITY_DESCRIPTOR) Buf.Buf
);
}
//
// If a user-specified ACL is found, replace it. We do this
// because doing a search/replace of ACEs is somewhat
// sophisticated. Could be implmented here.
//
if (Buf.End && IsValidSecurityDescriptor ((PSECURITY_DESCRIPTOR) Buf.Buf)) {
if (!GetSecurityDescriptorDacl (
(PSECURITY_DESCRIPTOR) Buf.Buf,
&Present,
&Acl,
&Defaulted
)) {
Defaulted = TRUE;
}
if (!Defaulted && Present) {
//
// The user specified an ACL. Replace it.
//
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Updating security for %s", SubPath));
SetNamedSecurityInfo (
SubPath,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
NULL,
NULL,
NewAcl,
NULL
);
} else {
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Not updating security for %s", SubPath));
}
} else {
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Path %s has no security descriptor", SubPath));
}
}
//
// Rename file/directory or recurse on directory
//
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
wsprintf (SubPath, L"%s\\%s", CurrentPath, fd.cFileName);
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)) {
break;
}
}
}
} else {
u = 0;
}
//
// If this path is not to be ignored, recursively fix it
//
if (!IgnoreDirList || !IgnoreDirList[u].Path) {
pFixDirReference (
SubPath,
ExistingSidString,
NewSidString,
NewAcl,
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
//
wsprintf (SubPath, L"%s\\%s", CurrentPath, ExistingSidString);
wsprintf (NewPath, L"%s\\%s", CurrentPath, NewSidString);
DEBUGMSG((DM_VERBOSE, "pFixDirReference: Moving %s to %s", SubPath, NewPath));
MoveFile (SubPath, NewPath);
}
} while (FindNextFile (hFind, &fd));
FindClose (hFind);
}
FreeGrowBuffer (&Buf);
}
BOOL
pOurExpandEnvironmentStrings (
IN PCWSTR String,
OUT PWSTR OutBuffer,
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
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;
CurrentString = String;
//
// Special case -- replace %APPDATA% with the app data from the user hive
//
if (UserHive && DoesStringHavePrefixW (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
//
lstrcpy (TempBuf1, UserProfile);
lstrcat (TempBuf1, L"\\*");
} else {
DEBUGMSG ((DM_VERBOSE, "Got AppData path from user hive: %s", TempBuf1));
}
lstrcat (TempBuf1, CurrentString + 9);
CurrentString = TempBuf1;
}
//
// Special case -- replace %USERPROFILE% with ProfileRoot, because
// our environment is for another user
//
if (UserProfile && DoesStringHavePrefixW (L"%USERPROFILE%", CurrentString)) {
lstrcpy (TempBuf2, UserProfile);
lstrcat (TempBuf2, CurrentString + 13);
CurrentString = TempBuf2;
}
//
// Now replace other environment variables
//
Size = ExpandEnvironmentStrings (CurrentString, OutBuffer, MAX_PATH);
if (Size && Size < MAX_PATH) {
return TRUE;
}
*OutBuffer = 0;
return FALSE;
}
VOID
pOurGetProfileRoot (
IN PCWSTR SidString,
OUT PWSTR ProfileRoot
)
/*++
Routine Description:
pOurGetProfileRoot queries the ProfileRoot key to find the root of the
user's profile.
Arguments:
SidString - Specifies the string version of the user's SID. The SID is
used to find the profile root.
ProfileRoot - Receives the profile root, or an empty string if the profile
root can't be obtained. The return value might also be the
root of all user profiles, if SidString is not match a valid
user.
Return Value:
None.
--*/
{
HKEY Key;
HKEY SidKey;
LONG rc;
WCHAR ProfilesDir[MAX_PATH];
DWORD Size;
*ProfileRoot = 0;
*ProfilesDir = 0;
rc = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
PROFILE_LIST_PATH,
0,
KEY_READ,
&Key
);
if (rc != ERROR_SUCCESS) {
DEBUGMSG ((DM_WARNING, "pOurGetProfileRoot failed with rc=%u", rc));
return;
}
Size = sizeof(ProfilesDir);
rc = RegQueryValueEx (
Key,
L"ProfilesDirectory",
NULL,
NULL,
(PBYTE) ProfilesDir,
&Size
);
if (rc != ERROR_SUCCESS) {
DEBUGMSG ((DM_WARNING, "pOurGetProfileRoot: Can't get profile root (rc=%u)", rc));
}
rc = RegOpenKeyEx (
Key,
SidString,
0,
KEY_READ,
&SidKey
);
if (rc == ERROR_SUCCESS) {
Size = MAX_PATH * sizeof (WCHAR);
rc = RegQueryValueEx (
SidKey,
L"ProfileImagePath",
NULL,
NULL,
(PBYTE) ProfilesDir,
&Size
);
if (rc != ERROR_SUCCESS) {
DEBUGMSG ((DM_WARNING, "pOurGetProfileRoot: SID key exists, but ProfileImagePath can't be queried (rc=%u)", rc));
}
RegCloseKey (SidKey);
}
RegCloseKey (Key);
pOurExpandEnvironmentStrings (ProfilesDir, ProfileRoot, NULL, NULL);
}
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;
PWSTR NewSidString;
UINT u;
WCHAR ExpandedRoot[MAX_PATH];
//PACL NewAcl;
WCHAR ProfileRoot[MAX_PATH];
HKEY UserHive;
WCHAR HivePath[MAX_PATH + 14];
LONG rc;
PCWSTR RegRoots[] = {
L"HKLM\\SOFTWARE\\Microsoft\\Protected Storage System Provider",
L"HKLM\\SOFTWARE\\Microsoft\\EventSystem",
L"HKLM\\SOFTWARE\\Microsoft\\Installer\\Managed",
L"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
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)) {
return;
}
if (!OurConvertSidToStringSid (NewSid, &NewSidString)) {
DeleteSidString (ExistingSidString);
return;
}
//
// Initialize directory strings and load the user hive
//
pOurGetProfileRoot (NewSidString, ProfileRoot);
DEBUGMSG ((DM_VERBOSE, "ProfileRoot (NewSid): %s", ProfileRoot));
wsprintf (HivePath, L"%s\\ntuser.dat", ProfileRoot);
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;
}
} else {
DEBUGMSG ((DM_WARNING, "RemapAndMoveUserW: Can't load user's hive, rc=%u", rc));
UserHive = NULL;
}
for (u = 0 ; IgnoreDirList[u].Path ; u++) {
pOurExpandEnvironmentStrings (
IgnoreDirList[u].Path,
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] ; u++) {
RegistrySearchAndReplaceW (
RegRoots[u],
ExistingSidString,
NewSidString
);
}
//
// Create a new ACL
//
//NewAcl = CreateDefaultAcl (NewSid);
//
// Test for directories and rename them
//
for (u = 0 ; DirList[u] ; u++) {
if (pOurExpandEnvironmentStrings (DirList[u], ExpandedRoot, ProfileRoot, UserHive)) {
pFixDirReference (
ExpandedRoot,
ExistingSidString,
NewSidString,
NULL,
IgnoreDirList
);
}
}
//
// Fix profile directory
//
pFixDirReference (
ProfileRoot,
ExistingSidString,
NewSidString,
NULL /* NewAcl */,
IgnoreDirList
);
//
// Crypto special case -- blow away ProtectedRoots key (413828)
//
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key, code is currently disabled"));
if (UserHive) {
if (!RegDelnode (UserHive, L"Software\\Microsoft\\SystemCertificates\\Root\\ProtectedRoots")) {
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key, GLE=%u", GetLastError()));
}
} else {
DEBUGMSG ((DM_WARNING, "Can't remove protected roots key because user hive could not be opened"));
}
//
// Cleanup
//
if (UserHive) {
RegCloseKey (UserHive);
RegUnLoadKey (HKEY_LOCAL_MACHINE, REMAP_KEY_NAME);
}
//FreeDefaultAcl (NewAcl);
DeleteSidString (ExistingSidString);
DeleteSidString (NewSidString);
}