2839 lines
69 KiB
C
2839 lines
69 KiB
C
|
/*++
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
|