]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Fix NSIS uninstall to work with UAC
authorKevin Wasserman <kevin.wasserman@painless-security.com>
Tue, 5 Jun 2012 17:03:21 +0000 (13:03 -0400)
committerTom Yu <tlyu@mit.edu>
Mon, 27 Aug 2012 23:27:35 +0000 (19:27 -0400)
Use ShellExecuteEx() to elevate privilege if CreateProcess() fails.

Signed-off-by: Kevin Wasserman <kevin.wasserman@painless-security.com>
(cherry picked from commit d66fcb1784fc6b5a6b01748dda7f99e0afa3fc69)

ticket: 7265
status: resolved

src/windows/installer/wix/custom/custom.cpp

index 5f0f42f65556c89e141e0d68e32bbe5ae1c9453a..3ef726d1dea02fb357610ff0f2441c8ea4a47a92 100644 (file)
@@ -83,11 +83,13 @@ SOFTWARE.
 // Only works for Win2k and above
 #define _WIN32_WINNT 0x500
 #include "custom.h"
+#include <shellapi.h>
 
 // linker stuff
 #pragma comment(lib, "msi")
 #pragma comment(lib, "advapi32")
-
+#pragma comment(lib, "shell32")
+#pragma comment(lib, "user32")
 
 void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){
        MSIHANDLE hRecord;
@@ -102,6 +104,22 @@ void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){
        MsiCloseHandle( hRecord );
 }
 
+static void ShowMsiErrorEx(MSIHANDLE hInstall, DWORD errcode, LPTSTR str,
+                           DWORD param )
+{
+    MSIHANDLE hRecord;
+
+    hRecord = MsiCreateRecord(3);
+    MsiRecordClearData(hRecord);
+    MsiRecordSetInteger(hRecord, 1, errcode);
+    MsiRecordSetString(hRecord, 2, str);
+    MsiRecordSetInteger(hRecord, 3, param);
+
+    MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecord);
+
+    MsiCloseHandle(hRecord);
+}
+
 #define LSA_KERBEROS_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos"
 #define LSA_KERBEROS_PARM_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Parameters"
 #define KFW_CLIENT_KEY "SOFTWARE\\MIT\\Kerberos\\Client\\"
@@ -520,130 +538,189 @@ _cleanup:
     return rv;
 }
 
-/* Uninstall NSIS */
-MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall )
+static bool IsNSISInstalled()
 {
-       DWORD rv = ERROR_SUCCESS;
-       // lookup the NSISUNINSTALL property value
-       LPTSTR cNsisUninstall = _T("UPGRADENSIS");
-       HANDLE hIo = NULL;
-       DWORD dwSize = 0;
-       LPTSTR strPathUninst = NULL;
-       HANDLE hJob = NULL;
-       STARTUPINFO sInfo;
-       PROCESS_INFORMATION pInfo;
-
-       pInfo.hProcess = NULL;
-       pInfo.hThread = NULL;
-
-       rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize );
-       if(rv != ERROR_MORE_DATA) goto _cleanup;
-
-       strPathUninst = new TCHAR[ ++dwSize ];
-
-       rv = MsiGetProperty( hInstall, cNsisUninstall, strPathUninst, &dwSize );
-       if(rv != ERROR_SUCCESS) goto _cleanup;
-
-       // Create a process for the uninstaller
-       sInfo.cb = sizeof(sInfo);
-       sInfo.lpReserved = NULL;
-       sInfo.lpDesktop = _T("");
-       sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows");
-       sInfo.dwX = 0;
-       sInfo.dwY = 0;
-       sInfo.dwXSize = 0;
-       sInfo.dwYSize = 0;
-       sInfo.dwXCountChars = 0;
-       sInfo.dwYCountChars = 0;
-       sInfo.dwFillAttribute = 0;
-       sInfo.dwFlags = 0;
-       sInfo.wShowWindow = 0;
-       sInfo.cbReserved2 = 0;
-       sInfo.lpReserved2 = 0;
-       sInfo.hStdInput = 0;
-       sInfo.hStdOutput = 0;
-       sInfo.hStdError = 0;
-
-       if(!CreateProcess( 
-               strPathUninst,
-               _T("Uninstall /S"),
-               NULL,
-               NULL,
-               FALSE,
-               CREATE_SUSPENDED,
-               NULL,
-               NULL,
-               &sInfo,
-               &pInfo)) {
-            DWORD lastError = GetLastError();
-            MSIHANDLE hRecord;
-
-            hRecord = MsiCreateRecord(4);
-            MsiRecordClearData(hRecord);
-            MsiRecordSetInteger(hRecord, 1, ERR_NSS_FAILED_CP);
-            MsiRecordSetString(hRecord, 2, strPathUninst);
-            MsiRecordSetInteger(hRecord, 3, lastError);
-
-            MsiProcessMessage( hInstall, INSTALLMESSAGE_ERROR, hRecord );
-       
-            MsiCloseHandle( hRecord );
-
-            pInfo.hProcess = NULL;
-            pInfo.hThread = NULL;
-            rv = 40;
-            goto _cleanup;
-        };
-
-       // Create a job object to contain the NSIS uninstall process tree
-
-       JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp;
+    HKEY nsisKfwKey = NULL;
+    // Note: check Wow6432 node if 64 bit build
+    HRESULT res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+                               "SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
+                               "\\Uninstall\\Kerberos for Windows",
+                               0,
+                               KEY_READ | KEY_WOW64_32KEY,
+                               &nsisKfwKey);
+    if (res != ERROR_SUCCESS)
+        return FALSE;
+
+    RegCloseKey(nsisKfwKey);
+    return TRUE;
+}
 
-       acp.CompletionKey = 0;
+static HANDLE NSISUninstallShellExecute(LPTSTR pathUninstall)
+{
+    SHELLEXECUTEINFO   sei;
+    ZeroMemory ( &sei, sizeof(sei) );
+
+    sei.cbSize          = sizeof(sei);
+    sei.hwnd            = GetForegroundWindow();
+    sei.fMask           = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI |
+                          SEE_MASK_NOCLOSEPROCESS;
+    sei.lpVerb          = _T("runas"); // run as administrator
+    sei.lpFile          = pathUninstall;
+    sei.lpParameters    = _T("");
+    sei.nShow           = SW_SHOWNORMAL;
+
+    if (!ShellExecuteEx(&sei)) {
+        // FAILED! TODO: report details?
+    }
+    return sei.hProcess;
+}
 
-       hJob = CreateJobObject(NULL, _T("NSISUninstallObject"));
-       if(!hJob) {
-               rv = 41;
-               goto _cleanup;
-       }
+static HANDLE NSISUninstallCreateProcess(LPTSTR pathUninstall)
+{
+    STARTUPINFO sInfo;
+    PROCESS_INFORMATION pInfo;
+    pInfo.hProcess = NULL;
+    pInfo.hThread = NULL;
+
+    // Create a process for the uninstaller
+    sInfo.cb = sizeof(sInfo);
+    sInfo.lpReserved = NULL;
+    sInfo.lpDesktop = _T("");
+    sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows");
+    sInfo.dwX = 0;
+    sInfo.dwY = 0;
+    sInfo.dwXSize = 0;
+    sInfo.dwYSize = 0;
+    sInfo.dwXCountChars = 0;
+    sInfo.dwYCountChars = 0;
+    sInfo.dwFillAttribute = 0;
+    sInfo.dwFlags = 0;
+    sInfo.wShowWindow = 0;
+    sInfo.cbReserved2 = 0;
+    sInfo.lpReserved2 = 0;
+    sInfo.hStdInput = 0;
+    sInfo.hStdOutput = 0;
+    sInfo.hStdError = 0;
+
+    if (!CreateProcess(pathUninstall,
+                       _T("Uninstall /S"),
+                       NULL,
+                       NULL,
+                       FALSE,
+                       CREATE_SUSPENDED,
+                       NULL,
+                       NULL,
+                       &sInfo,
+                       &pInfo)) {
+        // failure; could grab info, but we should be able to recover by
+        // using NSISUninstallShellExecute...
+    } else {
+        // success
+        // start up the thread
+        ResumeThread(pInfo.hThread);
+        // done with thread handle
+        CloseHandle(pInfo.hThread);
+    }
+    return pInfo.hProcess;
+}
 
-       hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
-       if(!hIo) {
-               rv = 42;
-               goto _cleanup;
-       }
 
-       acp.CompletionPort = hIo;
+/* Uninstall NSIS */
+MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall )
+{
+    DWORD rv = ERROR_SUCCESS;
+    DWORD lastError;
+    // lookup the NSISUNINSTALL property value
+    LPTSTR cNsisUninstall = _T("UPGRADENSIS");
+    LPTSTR strPathUninst = NULL;
+    DWORD dwSize = 0;
+    HANDLE hProcess = NULL;
+    HANDLE hIo = NULL;
+    HANDLE hJob = NULL;
+
+    rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize );
+    if(rv != ERROR_MORE_DATA) goto _cleanup;
+
+    strPathUninst = new TCHAR[ ++dwSize ];
+
+    rv = MsiGetProperty(hInstall, cNsisUninstall, strPathUninst, &dwSize);
+    if(rv != ERROR_SUCCESS) goto _cleanup;
+
+    hProcess = NSISUninstallCreateProcess(strPathUninst);
+    if (hProcess == NULL) // expected when run on UAC-limited account
+        hProcess = NSISUninstallShellExecute(strPathUninst);
+
+    if (hProcess == NULL) {
+        // still no uninstall process? ick...
+        lastError = GetLastError();
+        rv = 40;
+        goto _cleanup;
+    }
+    // note that it is not suffiecient to wait for the initial process to
+    // finish; there is a whole process tree that we need to wait for.  sigh.
+    JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp;
+    acp.CompletionKey = 0;
+    hJob = CreateJobObject(NULL, _T("NSISUninstallObject"));
+    if(!hJob) {
+        rv = 41;
+        goto _cleanup;
+    }
 
-       SetInformationJobObject( hJob, JobObjectAssociateCompletionPortInformation, &acp, sizeof(acp));
+    hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
+    if(!hIo) {
+        rv = 42;
+        goto _cleanup;
+    }
 
-       AssignProcessToJobObject( hJob, pInfo.hProcess );
+    acp.CompletionPort = hIo;
+
+    SetInformationJobObject(hJob,
+                            JobObjectAssociateCompletionPortInformation,
+                            &acp,
+                            sizeof(acp));
+
+    AssignProcessToJobObject(hJob, hProcess);
+
+    DWORD msgId;
+    ULONG_PTR unusedCompletionKey;
+    LPOVERLAPPED unusedOverlapped;
+    for (;;) {
+        if (!GetQueuedCompletionStatus(hIo,
+                                       &msgId,
+                                       &unusedCompletionKey,
+                                       &unusedOverlapped,
+                                       INFINITE)) {
+            Sleep(1000);
+        } else if (msgId == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) {
+            break;
+        }
+    }
 
-       ResumeThread( pInfo.hThread );
+_cleanup:
+    if (hProcess) CloseHandle(hProcess);
+    if (hIo) CloseHandle(hIo);
+    if (hJob) CloseHandle(hJob);
+
+    if (IsNSISInstalled()) {
+        // uninstall failed: maybe user cancelled uninstall, or something else
+        // went wrong...
+        if (rv == ERROR_SUCCESS)
+            rv = 43;
+    } else {
+        // Maybe something went wrong, but it doesn't matter as long as nsis
+        // is gone now...
+        rv = ERROR_SUCCESS;
+    }
 
-       DWORD a,b,c;
-       for(;;) {
-               if(!GetQueuedCompletionStatus(hIo, &a, (PULONG_PTR) &b, (LPOVERLAPPED *) &c, INFINITE)) {
-                       Sleep(1000);
-                       continue;
-               }
-               if(a == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) {
-                       break;
-               }
-       }
+    if (rv == 40) {
+        // CreateProcess() / ShellExecute() errors get extra data
+        ShowMsiErrorEx(hInstall, ERR_NSS_FAILED_CP, strPathUninst, lastError);
+    } else if (rv != ERROR_SUCCESS) {
+        ShowMsiError(hInstall, ERR_NSS_FAILED, rv);
+    }
 
-       rv = ERROR_SUCCESS;
-    
-_cleanup:
-       if(hIo) CloseHandle(hIo);
-       if(pInfo.hProcess)      CloseHandle( pInfo.hProcess );
-       if(pInfo.hThread)       CloseHandle( pInfo.hThread );
-       if(hJob) CloseHandle(hJob);
-       if(strPathUninst) delete strPathUninst;
-
-       if(rv != ERROR_SUCCESS && rv != 40) {
-            ShowMsiError( hInstall, ERR_NSS_FAILED, rv );
-       }
-       return rv;
+    if (strPathUninst) delete strPathUninst;
+    return rv;
 }
 
 /* Check and add or remove networkprovider key value