Index: /trunk/src/VBox/Runtime/r3/win/process-win.cpp
===================================================================
--- /trunk/src/VBox/Runtime/r3/win/process-win.cpp	(revision 29715)
+++ /trunk/src/VBox/Runtime/r3/win/process-win.cpp	(revision 29716)
@@ -33,4 +33,5 @@
 #include <Userenv.h>
 #include <Windows.h>
+#include <tlhelp32.h>
 #include <process.h>
 #include <errno.h>
@@ -245,178 +246,13 @@
                                  STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags)
 {
-    HANDLE hToken = INVALID_HANDLE_VALUE;
+    int rc = VINF_SUCCESS;
     BOOL fRc = FALSE;
-#ifndef IPRT_TARGET_NT4
+    DWORD dwErr = NO_ERROR;
+
     /*
-     * Get the session ID.
-     */
-    DWORD dwSessionID = 0; /* On W2K the session ID is always 0 (does not have fast user switching). */
-    RTLDRMOD hKernel32;
-    int rc = RTLdrLoad("Kernel32.dll", &hKernel32);
-    if (RT_SUCCESS(rc))
-    {
-        PFNWTSGETACTIVECONSOLESESSIONID pfnWTSGetActiveConsoleSessionId;
-        rc = RTLdrGetSymbol(hKernel32, "WTSGetActiveConsoleSessionId", (void **)&pfnWTSGetActiveConsoleSessionId);
-        if (RT_SUCCESS(rc))
-        {
-            /*
-             * Console session means the session which the physical keyboard and mouse
-             * is connected to. Due to FUS (fast user switching) starting with Windows XP
-             * this can be a different session than 0.
-             */
-            dwSessionID = pfnWTSGetActiveConsoleSessionId(); /* Get active console session ID. */
-        }
-        RTLdrClose(hKernel32);
-    }
-
-    if (fFlags & RTPROC_FLAGS_SERVICE)
-    {
-        /*
-         * Get the current user token.
-         */
-        RTLDRMOD hWtsAPI32;
-        rc = RTLdrLoad("Wtsapi32.dll", &hWtsAPI32);
-        if (RT_SUCCESS(rc))
-        {
-            /* Note that WTSQueryUserToken() only is available on Windows XP and up! */
-            PFNWTSQUERYUSERTOKEN pfnWTSQueryUserToken;
-            rc = RTLdrGetSymbol(hWtsAPI32, "WTSQueryUserToken", (void **)&pfnWTSQueryUserToken);
-            if (RT_SUCCESS(rc))
-                fRc = pfnWTSQueryUserToken(dwSessionID, &hToken);
-            RTLdrClose(hWtsAPI32);
-        }
-    }
-#endif /* !IPRT_TARGET_NT4 */
-
-    DWORD dwErr = NO_ERROR;
-    /*
-     * If not run by a service, use the normal LogonUserW function in order
-     * to run the child process with different credentials using the returned
-     * primary token.
+     * If we run as a service CreateProcessWithLogon will fail,
+     * so don't even try it (because of Local System context).
      */
     if (!(fFlags & RTPROC_FLAGS_SERVICE))
-    {  
-        /*
-         * The following rights are needed in order to use LogonUserW and
-         * CreateProcessAsUserW, so the local policy has to be modified to:
-         *  - SE_TCB_NAME = Act as part of the operating system
-         *  - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object
-         *  - SE_INCREASE_QUOTA_NAME
-         *
-         * We may fail here with ERROR_PRIVILEGE_NOT_HELD.
-         */
-        fRc = LogonUserW(pwszUser,
-                          NULL,      /* lpDomain */
-                          pwszPassword,
-                          LOGON32_LOGON_INTERACTIVE,
-                          LOGON32_PROVIDER_DEFAULT,
-                          &hToken);
-    }
-
-    if (fRc)
-    {
-#ifndef IPRT_TARGET_NT4
-        HANDLE hDuplicatedToken = INVALID_HANDLE_VALUE;
-        HANDLE hProcessToken = INVALID_HANDLE_VALUE;
-
-        /*
-         * If used by a service, open this service process and adjust 
-         * privileges by enabling the SE_TCB_NAME right to act as part
-         * of the operating system. 
-         */
-        if (fFlags & RTPROC_FLAGS_SERVICE)
-        {           
-            TOKEN_PRIVILEGES privToken, privTokenOld;
-            DWORD dwTokenSize;
-        
-            fRc = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken);
-            if (   fRc
-                && LookupPrivilegeValue(NULL, SE_TCB_NAME, &privToken.Privileges[0].Luid))
-            {
-                privToken.PrivilegeCount = 1;
-                privToken.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
-                fRc = AdjustTokenPrivileges(hProcessToken, FALSE, &privToken, sizeof(privToken), &privTokenOld, &dwTokenSize);
-                if (!fRc)
-                    dwErr = GetLastError();
-            }
-        }
-    
-        if (   dwErr == ERROR_SUCCESS
-            || dwErr == ERROR_NOT_ALL_ASSIGNED) /* The AdjustTokenPrivileges() did not succeed, but this isn't fatal. */
-        {            
-            fRc = DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken);
-#if 0
-            /*
-             * Assign the current session ID (> 0 on Windows Vista and up) to the primary token we just duplicated.
-             ** @todo This does not work yet, needs more investigation! */
-             */
-            if (fRc)
-            {
-                fRc = SetTokenInformation(hDuplicatedToken, TokenSessionId, &dwSessionID, sizeof(DWORD));
-                if (!fRc)
-                    dwErr = GetLastError();
-            }
-            else
-                dwErr = GetLastError();
-#endif
-        }
-
-#endif /* !IPRT_TARGET_NT4 */
-
-        /** @todo On NT4 we need to enable the SeTcbPrivilege to act as part of the operating system. Otherwise
-          *       we will get error 1314 (priviledge not held) as a response. */
-
-        /* Hopefully having a valid token now! */
-        if (   fRc
-#ifdef IPRT_TARGET_NT4
-            && hToken           != INVALID_HANDLE_VALUE
-#else
-            && hDuplicatedToken != INVALID_HANDLE_VALUE
-#endif /* IPRT_TARGET_NT4 */
-            )
-        {   
-            fRc = CreateProcessAsUserW(
-#ifdef IPRT_TARGET_NT4
-                                       hToken,
-#else
-                                       hDuplicatedToken,
-#endif /* !IPRT_TARGET_NT4 */
-                                       pwszExec,
-                                       pwszCmdLine,
-                                       NULL,         /* pProcessAttributes */
-                                       NULL,         /* pThreadAttributes */
-                                       TRUE,         /* fInheritHandles */
-                                       dwCreationFlags,
-                                       pwszzBlock,
-                                       NULL,         /* pCurrentDirectory */
-                                       pStartupInfo,
-                                       pProcInfo);
-            if (!fRc)
-                dwErr = GetLastError();
-#ifndef IPRT_TARGET_NT4
-            CloseHandle(hDuplicatedToken);
-#endif /* !IPRT_TARGET_NT4 */
-        }
-        CloseHandle(hToken);
-#ifndef IPRT_TARGET_NT4
-        if (fFlags & RTPROC_FLAGS_SERVICE)
-        {
-            /** @todo Drop SE_TCB_NAME priviledge before closing the process handle! */
-            if (hProcessToken != INVALID_HANDLE_VALUE)
-                CloseHandle(hProcessToken);
-        }
-#endif /* !IPRT_TARGET_NT4 */
-    }
-    else
-        dwErr = GetLastError();
-
-#ifndef IPRT_TARGET_NT4
-    /*
-     * If we don't hold enough priviledges to spawn a new process with
-     * different credentials we have to use CreateProcessWithLogonW here.  This
-     * API is W2K+ and uses a system service for spawning the process.
-     */
-    /** @todo Use fFlags to either use this feature or just fail. */
-    if (dwErr == ERROR_PRIVILEGE_NOT_HELD)
     {
         RTLDRMOD hAdvAPI32;
@@ -424,5 +260,9 @@
         if (RT_SUCCESS(rc))
         {
-            /* This may fail on too old (NT4) platforms. */
+            /*
+             * This may fail on too old (NT4) platforms or if the calling process
+             * is running on a SYSTEM account (like a service, ERROR_ACCESS_DENIED) on newer
+             * platforms (however, this works on W2K!).
+             */
             PFNCREATEPROCESSWITHLOGON pfnCreateProcessWithLogonW;
             rc = RTLdrGetSymbol(hAdvAPI32, "CreateProcessWithLogonW", (void**)&pfnCreateProcessWithLogonW);
@@ -440,7 +280,5 @@
                                                  pStartupInfo,
                                                  pProcInfo);
-                if (fRc)
-                    dwErr = NO_ERROR;
-                else
+                if (!fRc)
                     dwErr = GetLastError();
             }
@@ -448,8 +286,261 @@
         }
     }
-#endif /* !IPRT_TARGET_NT4 */
+
+    /*
+     * Get the session ID.
+     */
+    DWORD dwSessionID = 0; /* On W2K the session ID is always 0 (does not have fast user switching). */
+    RTLDRMOD hKernel32;
+    rc = RTLdrLoad("Kernel32.dll", &hKernel32);
+    if (RT_SUCCESS(rc))
+    {
+        PFNWTSGETACTIVECONSOLESESSIONID pfnWTSGetActiveConsoleSessionId;
+        rc = RTLdrGetSymbol(hKernel32, "WTSGetActiveConsoleSessionId", (void **)&pfnWTSGetActiveConsoleSessionId);
+        if (RT_SUCCESS(rc))
+        {
+            /*
+             * Console session means the session which the physical keyboard and mouse
+             * is connected to. Due to FUS (fast user switching) starting with Windows XP
+             * this can be a different session than 0.
+             */
+            dwSessionID = pfnWTSGetActiveConsoleSessionId(); /* Get active console session ID. */
+        }
+        RTLdrClose(hKernel32);
+    }
+
+    /*
+     * Did the API call above fail because we're running on a too old OS (NT4) or
+     * we're running as a Windows service?
+     */
+    if (   RT_FAILURE(rc)
+        || (fFlags & RTPROC_FLAGS_SERVICE))
+    {
+        /*
+         * First we have to validate the credentials. If they're valid we can
+         * proceed. This is important when running as a service which then looks up
+         * the current session the user is logged on in order to start the actual
+         * process there.
+         *
+         * The following rights are needed in order to use LogonUserW and
+         * CreateProcessAsUserW, so the local policy has to be modified to:
+         *  - SE_TCB_NAME = Act as part of the operating system
+         *  - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object
+         *  - SE_INCREASE_QUOTA_NAME
+         *
+         * We may fail here with ERROR_PRIVILEGE_NOT_HELD.
+         *
+         ** @todo Deal with http://support.microsoft.com/kb/245683 for NULL domain names
+         * on NT4 (ignored here by now). Passing FQDNs should work!
+         */
+        PHANDLE phToken = NULL;
+        HANDLE hTokenLogon = INVALID_HANDLE_VALUE;
+        fRc = LogonUserW(pwszUser,
+                         NULL,
+                         pwszPassword,
+                         LOGON32_LOGON_INTERACTIVE,
+                         LOGON32_PROVIDER_DEFAULT,
+                         &hTokenLogon);
+
+        BOOL bFound = FALSE;
+        HANDLE hTokenVBoxTray = INVALID_HANDLE_VALUE;
+        if (fRc)
+        {
+            if (fFlags & RTPROC_FLAGS_SERVICE)
+            {
+                DWORD cbName = 0; /* Must be zero to query size! */
+                DWORD cbDomain = 0;
+                SID_NAME_USE sidNameUse = SidTypeUser;
+                fRc = LookupAccountNameW(NULL,
+                                         pwszUser,
+                                         NULL,
+                                         &cbName,
+                                         NULL,
+                                         &cbDomain,
+                                         &sidNameUse);
+                if (!fRc)
+                    dwErr = GetLastError();
+                if (   !fRc
+                    && dwErr == ERROR_INSUFFICIENT_BUFFER
+                    && cbName > 0)
+                {
+                    dwErr = NO_ERROR;
+    
+                    PSID pSID = (PSID)RTMemAlloc(cbName * sizeof(wchar_t));
+                    AssertPtrReturn(pSID, VERR_NO_MEMORY);
+    
+                    /** @todo No way to allocate a PRTUTF16 directly? */
+                    char *pszDomainUtf8 = NULL;
+                    PRTUTF16 pwszDomain = NULL;
+                    if (cbDomain > 0)
+                    {
+                        pszDomainUtf8 = RTStrAlloc(cbDomain);
+                        rc = RTStrToUtf16(pszDomainUtf8, &pwszDomain);
+                        AssertRCReturn(rc, rc);
+                        RTStrFree(pszDomainUtf8);
+                    }
+    
+                    /* Note: Also supports FQDNs! */
+                    if (   LookupAccountNameW(NULL,            /* lpSystemName */
+                                              pwszUser,
+                                              pSID,
+                                              &cbName,
+                                              pwszDomain,
+                                              &cbDomain,
+                                              &sidNameUse)
+                        && IsValidSid(pSID))
+                    {
+                        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+                        if (hSnap != INVALID_HANDLE_VALUE)
+                        {
+                            PROCESSENTRY32 procEntry;
+                            procEntry.dwSize = sizeof(PROCESSENTRY32);
+                            if (Process32First(hSnap, &procEntry))
+                            {        
+                                DWORD dwVBoxTrayPID = 0;
+                                DWORD dwCurSession = 0;
+                                do
+                                {
+                                    if (   _stricmp(procEntry.szExeFile, "VBoxTray.exe") == 0
+                                        && ProcessIdToSessionId(procEntry.th32ProcessID, &dwCurSession))
+                                    {
+                                        HANDLE hProc = OpenProcess(MAXIMUM_ALLOWED, TRUE, procEntry.th32ProcessID);
+                                        if (hProc != NULL)
+                                        {
+                                            HANDLE hTokenProc;
+                                            fRc = OpenProcessToken(hProc,
+                                                                   TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE |
+                                                                   TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
+                                                                   &hTokenProc);
+                                            if (fRc)
+                                            {
+                                                DWORD dwSize = 0;
+                                                fRc = GetTokenInformation(hTokenProc, TokenUser, NULL, 0, &dwSize);
+                                                if (!fRc)
+                                                    dwErr = GetLastError();
+                                                if (   !fRc
+                                                    && dwErr == ERROR_INSUFFICIENT_BUFFER
+                                                    && dwSize > 0)
+                                                {
+                                                    PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemAlloc(dwSize);
+                                                    AssertPtrBreak(pTokenUser);
+                                                    RT_ZERO(*pTokenUser);
+                                                    if (   GetTokenInformation(hTokenProc,
+                                                                               TokenUser,
+                                                                               (LPVOID)pTokenUser,
+                                                                               dwSize,
+                                                                               &dwSize)
+                                                        && IsValidSid(pTokenUser->User.Sid)
+                                                        && EqualSid(pTokenUser->User.Sid, pSID))
+                                                    {
+                                                        if (DuplicateTokenEx(hTokenProc, MAXIMUM_ALLOWED,
+                                                                             NULL, SecurityIdentification, TokenPrimary, &hTokenVBoxTray))
+                                                        {
+                                                            /* 
+                                                             * So we found a VBoxTray instance which belongs to the user we want to
+                                                             * to run our new process under. This duplicated token will be used for
+                                                             * the actual CreateProcessAsUserW() call then. 
+                                                             */
+                                                            bFound = TRUE;                                                        
+                                                        }
+                                                        else
+                                                            dwErr = GetLastError();
+                                                    }
+                                                    else
+                                                        dwErr = GetLastError();
+                                                    RTMemFree(pTokenUser);
+                                                }
+                                                else
+                                                    dwErr = GetLastError();
+                                                CloseHandle(hTokenProc);
+                                            }
+                                            else
+                                                dwErr = GetLastError();
+                                            CloseHandle(hProc);
+                                        }
+                                        else
+                                            dwErr = GetLastError();
+                                    }
+                                } while (Process32Next(hSnap, &procEntry) && !bFound);
+                            }
+                            else /* Process32First */
+                                dwErr = GetLastError();
+                            CloseHandle(hSnap);
+                        }
+                        else /* hSnap =! INVALID_HANDLE_VALUE */
+                            dwErr = GetLastError();
+                    }
+                    else
+                        dwErr = GetLastError(); /* LookupAccountNameW() failed. */
+                    RTMemFree(pSID);
+                    if (pwszDomain != NULL)
+                        RTUtf16Free(pwszDomain);
+                }
+            }
+            else /* !RTPROC_FLAGS_SERVICE */
+            {
+                /* Nothing to do here right now. */
+            }
+
+            /* 
+             * If we didn't find a matching VBoxTray, just use the token we got
+             * above from LogonUserW(). This enables us to at least run processes with
+             * desktop interaction without UI.
+             */
+            phToken = bFound ? &hTokenVBoxTray : &hTokenLogon;
+
+            /*
+             * Useful KB articles:
+             * http://support.microsoft.com/kb/165194/
+             * http://support.microsoft.com/kb/184802/
+             * http://support.microsoft.com/kb/327618/
+             */
+            fRc = CreateProcessAsUserW(*phToken,
+                                       pwszExec,
+                                       pwszCmdLine,
+                                       NULL,         /* pProcessAttributes */
+                                       NULL,         /* pThreadAttributes */
+                                       TRUE,         /* fInheritHandles */
+                                       dwCreationFlags,
+                                       pwszzBlock,
+                                       NULL,         /* pCurrentDirectory */
+                                       pStartupInfo,
+                                       pProcInfo);
+            if (fRc)
+                dwErr = NO_ERROR;
+            else
+                dwErr = GetLastError(); /* CreateProcessAsUserW() failed. */
+
+            if (hTokenVBoxTray != INVALID_HANDLE_VALUE)
+                CloseHandle(hTokenVBoxTray);
+            CloseHandle(hTokenLogon);
+        }
+        else
+            dwErr = GetLastError(); /* LogonUserW() failed. */
+    }
 
     if (dwErr != NO_ERROR)
-        rc = RTErrConvertFromWin32(dwErr);
+    {
+        /*
+         * Map some important or much used Windows error codes
+         * to our error codes.
+         */
+        switch (dwErr)
+        {
+            case ERROR_NOACCESS:
+            case ERROR_PRIVILEGE_NOT_HELD:
+                rc = VERR_PERMISSION_DENIED;
+                break;
+
+            case ERROR_PASSWORD_EXPIRED:
+            case ERROR_ACCOUNT_RESTRICTION:
+                rc = VERR_LOGON_FAILURE;
+                break;
+
+            default:
+                /* Could trigger a debug assertion! */
+                rc = RTErrConvertFromWin32(dwErr);
+                break;
+        }
+    }
     else
         rc = VINF_SUCCESS;
@@ -586,5 +677,12 @@
                 PROCESS_INFORMATION ProcInfo;
                 RT_ZERO(ProcInfo);
-                if (pszAsUser == NULL)
+
+                /*
+                 * Only use the normal CreateProcess stuff if we have no user name
+                 * and we are not running from a (Windows) service. Otherwise use
+                 * the more advanced version in rtProcCreateAsUserHlp().
+                 */
+                if (   pszAsUser == NULL
+                    && !(fFlags & RTPROC_FLAGS_SERVICE))
                 {
                     if (CreateProcessW(pwszExec,
