From 4b3f52f14bce8a032fcc476556cc4d60aa20241b Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 11 Apr 2021 14:15:32 -0700 Subject: Fix rollback of user rights --- src/ca/scaexec.cpp | 433 +++++++++++++++++++++++++++++++++++++++++++---------- src/ca/scauser.cpp | 33 ++++ src/ca/utilca.def | 1 + 3 files changed, 388 insertions(+), 79 deletions(-) (limited to 'src/ca') diff --git a/src/ca/scaexec.cpp b/src/ca/scaexec.cpp index ab9e6599..5845c1b4 100644 --- a/src/ca/scaexec.cpp +++ b/src/ca/scaexec.cpp @@ -293,6 +293,110 @@ LExit: } +static HRESULT GetUserHasRight( + __in LSA_HANDLE hPolicy, + __in PSID pUserSid, + __in LPWSTR wzRight, + __out BOOL* fHasRight +) +{ + HRESULT hr = S_OK; + NTSTATUS nt = 0; + LSA_UNICODE_STRING lucPrivilege = { 0 }; + PLSA_ENUMERATION_INFORMATION rgSids = NULL; + ULONG cSids = 0; + *fHasRight = FALSE; + + lucPrivilege.Buffer = wzRight; + lucPrivilege.Length = static_cast(lstrlenW(lucPrivilege.Buffer) * sizeof(WCHAR)); + lucPrivilege.MaximumLength = (lucPrivilege.Length + 1) * sizeof(WCHAR); + + nt = ::LsaEnumerateAccountsWithUserRight(hPolicy, &lucPrivilege, reinterpret_cast(&rgSids), &cSids); + hr = HRESULT_FROM_WIN32(::LsaNtStatusToWinError(nt)); + ExitOnFailure(hr, "Failed to enumerate users for right: %ls", lucPrivilege.Buffer); + + for (DWORD i = 0; i < cSids; ++i) + { + PLSA_ENUMERATION_INFORMATION pInfo = rgSids + i; + if (::EqualSid(pUserSid, pInfo->Sid)) + { + *fHasRight = TRUE; + break; + } + } + +LExit: + if (rgSids) + { + ::LsaFreeMemory(rgSids); + } + + return hr; +} + + +static HRESULT GetExistingUserRightsAssignments( + __in_opt LPCWSTR wzDomain, + __in LPCWSTR wzName, + __inout int* iAttributes +) +{ + HRESULT hr = S_OK; + NTSTATUS nt = 0; + BOOL fHasRight = FALSE; + + LSA_HANDLE hPolicy = NULL; + LSA_OBJECT_ATTRIBUTES objectAttributes = { 0 }; + + LPWSTR pwzUser = NULL; + PSID psid = NULL; + + if (wzDomain && *wzDomain) + { + hr = StrAllocFormatted(&pwzUser, L"%s\\%s", wzDomain, wzName); + ExitOnFailure(hr, "Failed to allocate user with domain string"); + } + else + { + hr = StrAllocString(&pwzUser, wzName, 0); + ExitOnFailure(hr, "Failed to allocate string from user name."); + } + + hr = AclGetAccountSid(NULL, pwzUser, &psid); + ExitOnFailure(hr, "Failed to get SID for user: %ls", pwzUser); + + nt = ::LsaOpenPolicy(NULL, &objectAttributes, POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION, &hPolicy); + hr = HRESULT_FROM_WIN32(::LsaNtStatusToWinError(nt)); + ExitOnFailure(hr, "Failed to open LSA policy store"); + + hr = GetUserHasRight(hPolicy, psid, L"SeServiceLogonRight", &fHasRight); + ExitOnFailure(hr, "Failed to check LogonAsService right"); + + if (fHasRight) + { + *iAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE; + } + + hr = GetUserHasRight(hPolicy, psid, L"SeBatchLogonRight", &fHasRight); + ExitOnFailure(hr, "Failed to check LogonAsBatchJob right"); + + if (fHasRight) + { + *iAttributes |= SCAU_ALLOW_LOGON_AS_BATCH; + } + +LExit: + if (hPolicy) + { + ::LsaClose(hPolicy); + } + + ReleaseSid(psid); + ReleaseStr(pwzUser); + return hr; +} + + static HRESULT ModifyUserLocalServiceRight( __in_opt LPCWSTR wzDomain, __in LPCWSTR wzName, @@ -466,6 +570,117 @@ static void SetUserPasswordAndAttributes( } +static HRESULT RemoveUserInternal( + LPWSTR wzGroupCaData, + LPWSTR wzDomain, + LPWSTR wzName, + int iAttributes +) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + LPWSTR pwz = NULL; + LPWSTR pwzGroup = NULL; + LPWSTR pwzGroupDomain = NULL; + LPCWSTR wz = NULL; + PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL; + + // + // Remove the logon as service privilege. + // + if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes) + { + hr = ModifyUserLocalServiceRight(wzDomain, wzName, FALSE); + if (FAILED(hr)) + { + WcaLogError(hr, "Failed to remove logon as service right from user, continuing..."); + hr = S_OK; + } + } + + if (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes) + { + hr = ModifyUserLocalBatchRight(wzDomain, wzName, FALSE); + if (FAILED(hr)) + { + WcaLogError(hr, "Failed to remove logon as batch job right from user, continuing..."); + hr = S_OK; + } + } + + // + // Remove the User Account if the user was created by us. + // + if (!(SCAU_DONT_CREATE_USER & iAttributes)) + { + if (wzDomain && *wzDomain) + { + er = ::DsGetDcNameW(NULL, (LPCWSTR)wzDomain, NULL, NULL, NULL, &pDomainControllerInfo); + if (RPC_S_SERVER_UNAVAILABLE == er) + { + // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag + er = ::DsGetDcNameW(NULL, (LPCWSTR)wzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo); + } + if (ERROR_SUCCESS == er) + { + wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix + } + else + { + wz = wzDomain; + } + } + + er = ::NetUserDel(wz, wzName); + if (NERR_UserNotFound == er) + { + er = NERR_Success; + } + ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "failed to delete user account: %ls", wzName); + } + else + { + // + // Remove the user from the groups + // + pwz = wzGroupCaData; + while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) + { + hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); + + if (FAILED(hr)) + { + WcaLogError(hr, "failed to get domain for group: %ls, continuing anyway.", pwzGroup); + } + else + { + hr = RemoveUserFromGroup(wzName, wzDomain, pwzGroup, pwzGroupDomain); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to remove user: %ls from group %ls, continuing anyway.", wzName, pwzGroup); + } + } + } + + if (E_NOMOREITEMS == hr) // if there are no more items, all is well + { + hr = S_OK; + } + + ExitOnFailure(hr, "failed to get next group from which to remove user:%ls", wzName); + } + +LExit: + if (pDomainControllerInfo) + { + ::NetApiBufferFree(static_cast(pDomainControllerInfo)); + } + + return hr; +} + + /******************************************************************** CreateUser - CUSTOM ACTION ENTRY POINT for creating users @@ -484,6 +699,7 @@ extern "C" UINT __stdcall CreateUser( LPWSTR pwz = NULL; LPWSTR pwzName = NULL; LPWSTR pwzDomain = NULL; + LPWSTR pwzScriptKey = NULL; LPWSTR pwzPassword = NULL; LPWSTR pwzGroup = NULL; LPWSTR pwzGroupDomain = NULL; @@ -491,6 +707,10 @@ extern "C" UINT __stdcall CreateUser( int iAttributes = 0; BOOL fInitializedCom = FALSE; + WCA_CASCRIPT_HANDLE hRollbackScript = NULL; + int iOriginalAttributes = 0; + int iRollbackAttributes = 0; + USER_INFO_1 userInfo; USER_INFO_1* puserInfo = NULL; DWORD dw; @@ -521,9 +741,44 @@ extern "C" UINT __stdcall CreateUser( hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); ExitOnFailure(hr, "failed to read attributes from custom action data"); + hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); + ExitOnFailure(hr, "failed to read encoding key from custom action data"); + hr = WcaReadStringFromCaData(&pwz, &pwzPassword); ExitOnFailure(hr, "failed to read password from custom action data"); + // There is no rollback scheduled if the key is empty. + // Best effort to get original configuration and save it in the script so rollback can restore it. + if (*pwzScriptKey) + { + hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript); + ExitOnFailure(hr, "Failed to open rollback CustomAction script."); + + iRollbackAttributes = 0; + hr = GetExistingUserRightsAssignments(pwzDomain, pwzName, &iOriginalAttributes); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to get existing user rights: %ls, continuing anyway.", pwzName); + } + else + { + if (!(SCAU_ALLOW_LOGON_AS_SERVICE & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes)) + { + iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE; + } + if (!(SCAU_ALLOW_LOGON_AS_BATCH & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes)) + { + iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_BATCH; + } + } + + hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes); + ExitOnFailure(hr, "Failed to add data to rollback script."); + + // Nudge the system to get all our rollback data written to disk. + WcaCaScriptFlush(hRollbackScript); + } + if (!(SCAU_DONT_CREATE_USER & iAttributes)) { ::ZeroMemory(&userInfo, sizeof(USER_INFO_1)); @@ -614,6 +869,8 @@ extern "C" UINT __stdcall CreateUser( ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName); LExit: + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); + if (puserInfo) { ::NetApiBufferFree((LPVOID)puserInfo); @@ -627,6 +884,7 @@ LExit: ReleaseStr(pwzData); ReleaseStr(pwzName); ReleaseStr(pwzDomain); + ReleaseStr(pwzScriptKey); ReleaseStr(pwzPassword); ReleaseStr(pwzGroup); ReleaseStr(pwzGroupDomain); @@ -650,15 +908,14 @@ LExit: /******************************************************************** - RemoveUser - CUSTOM ACTION ENTRY POINT for removing users + CreateUserRollback - CUSTOM ACTION ENTRY POINT for CreateUser rollback - Input: deferred CustomActionData - Name\tDomain * *****************************************************************/ -extern "C" UINT __stdcall RemoveUser( +extern "C" UINT __stdcall CreateUserRollback( MSIHANDLE hInstall - ) +) { - //AssertSz(0, "Debug RemoveAccount"); + //AssertSz(0, "Debug CreateUserRollback"); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; @@ -666,15 +923,16 @@ extern "C" UINT __stdcall RemoveUser( LPWSTR pwzData = NULL; LPWSTR pwz = NULL; LPWSTR pwzName = NULL; - LPWSTR pwzDomain= NULL; - LPWSTR pwzGroup = NULL; - LPWSTR pwzGroupDomain = NULL; + LPWSTR pwzDomain = NULL; + LPWSTR pwzScriptKey = NULL; int iAttributes = 0; - LPCWSTR wz = NULL; - PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL; BOOL fInitializedCom = FALSE; - hr = WcaInitialize(hInstall, "RemoveUser"); + WCA_CASCRIPT_HANDLE hRollbackScript = NULL; + LPWSTR pwzRollbackData = NULL; + int iOriginalAttributes = 0; + + hr = WcaInitialize(hInstall, "CreateUserRollback"); ExitOnFailure(hr, "failed to initialize"); hr = ::CoInitialize(NULL); @@ -690,6 +948,9 @@ extern "C" UINT __stdcall RemoveUser( // Read in the CustomActionData // pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); + ExitOnFailure(hr, "failed to read encoding key from custom action data"); + hr = WcaReadStringFromCaData(&pwz, &pwzName); ExitOnFailure(hr, "failed to read name from custom action data"); @@ -699,96 +960,110 @@ extern "C" UINT __stdcall RemoveUser( hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); ExitOnFailure(hr, "failed to read attributes from custom action data"); - // - // Remove the logon as service privilege. - // - if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes) + // Best effort to read original configuration from CreateUser. + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); + if (FAILED(hr)) { - hr = ModifyUserLocalServiceRight(pwzDomain, pwzName, FALSE); - if (FAILED(hr)) - { - WcaLogError(hr, "Failed to remove logon as service right from user, continuing..."); - hr = S_OK; - } + WcaLogError(hr, "Failed to open rollback CustomAction script, continuing anyway."); } - - if (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes) + else { - hr = ModifyUserLocalBatchRight(pwzDomain, pwzName, FALSE); + hr = WcaCaScriptReadAsCustomActionData(hRollbackScript, &pwzRollbackData); if (FAILED(hr)) { - WcaLogError(hr, "Failed to remove logon as batch job right from user, continuing..."); - hr = S_OK; + WcaLogError(hr, "Failed to read rollback script into CustomAction data, continuing anyway."); } - } - - // - // Remove the User Account if the user was created by us. - // - if (!(SCAU_DONT_CREATE_USER & iAttributes)) - { - if (pwzDomain && *pwzDomain) + else { - er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo ); - if (RPC_S_SERVER_UNAVAILABLE == er) - { - // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag - er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo ); - } - if (ERROR_SUCCESS == er) - { - wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix - } - else - { - wz = pwzDomain; - } - } - - er = ::NetUserDel(wz, pwzName); - if (NERR_UserNotFound == er) - { - er = NERR_Success; - } - ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "failed to delete user account: %ls", pwzName); - } - else - { - // - // Remove the user from the groups - // - while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) - { - hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); + WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData); + pwz = pwzRollbackData; + hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes); if (FAILED(hr)) { - WcaLogError(hr, "failed to get domain for group: %ls, continuing anyway.", pwzGroup); + WcaLogError(hr, "failed to read attributes from rollback data, continuing anyway"); } else { - hr = RemoveUserFromGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain); - if (FAILED(hr)) - { - WcaLogError(hr, "failed to remove user: %ls from group %ls, continuing anyway.", pwzName, pwzGroup); - } + iAttributes |= iOriginalAttributes; } } + } - if (E_NOMOREITEMS == hr) // if there are no more items, all is well - { - hr = S_OK; - } + hr = RemoveUserInternal(pwz, pwzDomain, pwzName, iAttributes); + +LExit: + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE); + + ReleaseStr(pwzData); + ReleaseStr(pwzName); + ReleaseStr(pwzDomain); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzRollbackData); - ExitOnFailure(hr, "failed to get next group from which to remove user:%ls", pwzName); + if (fInitializedCom) + { + ::CoUninitialize(); } -LExit: - if (pDomainControllerInfo) + if (FAILED(hr)) { - ::NetApiBufferFree(static_cast(pDomainControllerInfo)); + er = ERROR_INSTALL_FAILURE; } + return WcaFinalize(er); +} + + +/******************************************************************** + RemoveUser - CUSTOM ACTION ENTRY POINT for removing users + + Input: deferred CustomActionData - Name\tDomain + * *****************************************************************/ +extern "C" UINT __stdcall RemoveUser( + MSIHANDLE hInstall +) +{ + //AssertSz(0, "Debug RemoveUser"); + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + LPWSTR pwzData = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzName = NULL; + LPWSTR pwzDomain = NULL; + int iAttributes = 0; + BOOL fInitializedCom = FALSE; + + hr = WcaInitialize(hInstall, "RemoveUser"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + // + // Read in the CustomActionData + // + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzName); + ExitOnFailure(hr, "failed to read name from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzDomain); + ExitOnFailure(hr, "failed to read domain from custom action data"); + + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "failed to read attributes from custom action data"); + + hr = RemoveUserInternal(pwz, pwzDomain, pwzName, iAttributes); + +LExit: ReleaseStr(pwzData); ReleaseStr(pwzName); ReleaseStr(pwzDomain); diff --git a/src/ca/scauser.cpp b/src/ca/scauser.cpp index 0d87301f..b25e9daf 100644 --- a/src/ca/scauser.cpp +++ b/src/ca/scauser.cpp @@ -475,10 +475,19 @@ HRESULT ScaUserExecute( DWORD er = 0; PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL; + LPWSTR pwzBaseScriptKey = NULL; + DWORD cScriptKey = 0; + USER_INFO_0 *pUserInfo = NULL; + LPWSTR pwzScriptKey = NULL; LPWSTR pwzActionData = NULL; LPWSTR pwzRollbackData = NULL; + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + + // Loop through all the users to be configured. for (SCA_USER *psu = psuList; psu; psu = psu->psuNext) { USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE; @@ -555,6 +564,17 @@ HRESULT ScaUserExecute( // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER)) { + ++cScriptKey; + hr = StrAllocFormatted(&pwzScriptKey, L"%ls%u", pwzBaseScriptKey, cScriptKey); + ExitOnFailure(hr, "Failed to create encoding key."); + + // Write the script key to CustomActionData for install and rollback so information can be passed to rollback. + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add encoding key to custom action data."); + + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzRollbackData); + ExitOnFailure(hr, "Failed to add encoding key to rollback custom action data."); + INT iRollbackUserAttributes = psu->iAttributes; // If the user already exists, ensure this is accounted for in rollback @@ -567,6 +587,10 @@ HRESULT ScaUserExecute( iRollbackUserAttributes &= ~SCAU_DONT_CREATE_USER; } + // The deferred CA determines when to rollback User Rights Assignments so these should never be set. + iRollbackUserAttributes &= ~SCAU_ALLOW_LOGON_AS_SERVICE; + iRollbackUserAttributes &= ~SCAU_ALLOW_LOGON_AS_BATCH; + hr = WcaWriteStringToCaData(psu->wzName, &pwzRollbackData); ExitOnFailure(hr, "Failed to add user name to rollback custom action data: %ls", psu->wzName); hr = WcaWriteStringToCaData(psu->wzDomain, &pwzRollbackData); @@ -584,6 +608,12 @@ HRESULT ScaUserExecute( hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateUserRollback"), pwzRollbackData, COST_USER_DELETE); ExitOnFailure(hr, "failed to schedule CreateUserRollback"); } + else + { + // Write empty script key to CustomActionData since there is no rollback. + hr = WcaWriteStringToCaData(L"", &pwzActionData); + ExitOnFailure(hr, "Failed to add empty encoding key to custom action data."); + } // // Schedule the creation now. @@ -614,6 +644,7 @@ HRESULT ScaUserExecute( ExitOnFailure(hr, "failed to schedule RemoveUser"); } + ReleaseNullStr(pwzScriptKey); ReleaseNullStr(pwzActionData); ReleaseNullStr(pwzRollbackData); if (pUserInfo) @@ -629,6 +660,8 @@ HRESULT ScaUserExecute( } LExit: + ReleaseStr(pwzBaseScriptKey); + ReleaseStr(pwzScriptKey); ReleaseStr(pwzActionData); ReleaseStr(pwzRollbackData); if (pUserInfo) diff --git a/src/ca/utilca.def b/src/ca/utilca.def index 337c3a68..412d86a3 100644 --- a/src/ca/utilca.def +++ b/src/ca/utilca.def @@ -45,6 +45,7 @@ EXPORTS CreateSmb DropSmb CreateUser + CreateUserRollback RemoveUser ;scasched.cpp ConfigurePerfmonInstall -- cgit v1.2.3-55-g6feb