Make WinVNC service mode work on Windows Vista and beyond.
Patch by Jochen Tucht, fixes bug 135.



git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@5158 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/win/winvnc/VNCServerService.cxx b/win/winvnc/VNCServerService.cxx
index 2ef2ee0..1a2a8e9 100644
--- a/win/winvnc/VNCServerService.cxx
+++ b/win/winvnc/VNCServerService.cxx
@@ -20,6 +20,10 @@
 
 #include <winvnc/VNCServerService.h>
 #include <rfb_win32/OSVersion.h>
+#include <rfb_win32/TsSessions.h>
+#include <rfb_win32/ModuleFileName.h>
+#include <wtsapi32.h>
+#include <tlhelp32.h>
 
 using namespace winvnc;
 using namespace rfb;
@@ -27,9 +31,12 @@
 
 const TCHAR* winvnc::VNCServerService::Name = _T("WinVNC4");
 
-
-VNCServerService::VNCServerService(VNCServerWin32& s)
-  : Service(Name), server(s) {
+VNCServerService::VNCServerService()
+  : Service(Name)
+  , SendSas(_T("sas.dll"), "SendSAS")
+  , stopServiceEvent(CreateEvent(0, FALSE, FALSE, 0))
+  , sessionEvent(CreateEvent(0, FALSE, FALSE, "Global\\SessionEventTigerVNC"))
+  , sessionEventCad(CreateEvent(0, FALSE, FALSE, "Global\\SessionEventTigerVNCCad")) {
   // - Set the service-mode logging defaults
   //   These will be overridden by the Log option in the
   //   registry, if present.
@@ -40,13 +47,132 @@
 }
 
 
-DWORD VNCServerService::serviceMain(int argc, TCHAR* argv[]) {
-  setStatus(SERVICE_RUNNING);
-  int result = server.run();
-  setStatus(SERVICE_STOP_PENDING);
-  return result;
+//////////////////////////////////////////////////////////////////////////////
+
+DWORD GetLogonPid(DWORD dwSessionId)
+{
+    DWORD dwLogonPid = 0;
+    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    if (hSnap != INVALID_HANDLE_VALUE)
+    {
+        PROCESSENTRY32 procEntry;
+        procEntry.dwSize = sizeof procEntry;
+
+        if (Process32First(hSnap, &procEntry)) do
+        {
+            DWORD dwLogonSessionId = 0;
+            if (_stricmp(procEntry.szExeFile, "winlogon.exe") == 0 &&
+                ProcessIdToSessionId(procEntry.th32ProcessID, &dwLogonSessionId) &&
+                dwLogonSessionId == dwSessionId)
+            {
+                dwLogonPid = procEntry.th32ProcessID;
+                break;
+            }
+        } while (Process32Next(hSnap, &procEntry));
+        CloseHandle(hSnap);
+    }
+    return dwLogonPid;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken)
+{
+    BOOL bResult = FALSE;
+    ConsoleSessionId ID_session;
+    DWORD Id = GetLogonPid(ID_session.id);
+    if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id))
+    {
+        bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken);
+        CloseHandle(hProcess);
+    }
+    return bResult;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// START the app as system 
+HANDLE LaunchProcessWin(DWORD dwSessionId)
+{
+    HANDLE hProcess = NULL;
+    HANDLE hToken = NULL;
+    if (GetSessionUserTokenWin(&hToken))
+    {
+        ModuleFileName filename;
+        static const char cmdLineFmt[] = "\"%s\" -noconsole -service_run";
+        TCharArray cmdLine(_tcslen(filename.buf) + sizeof(cmdLineFmt)/sizeof(cmdLineFmt[0]));
+        _stprintf(cmdLine.buf, cmdLineFmt, filename.buf);
+        STARTUPINFO si;
+        ZeroMemory(&si, sizeof si);
+        si.cb = sizeof si;
+        si.dwFlags = STARTF_USESHOWWINDOW;
+        PROCESS_INFORMATION	pi;
+        if (CreateProcessAsUser(hToken, NULL, cmdLine.buf, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi))
+        {
+            CloseHandle(pi.hThread);
+            hProcess = pi.hProcess;
+        }
+        CloseHandle(hToken);
+    }
+    return hProcess;
+}
+
+DWORD VNCServerService::serviceMain(int argc, TCHAR* argv[])
+{
+    ConsoleSessionId OlddwSessionId;
+
+    HANDLE hProcess = NULL;
+    //We use this event to notify the program that the session has changed
+    //The program need to end so the service can restart the program in the correct session
+    //wait_for_existing_process();
+    HANDLE testevent[2] = { stopServiceEvent, sessionEventCad };
+    setStatus(SERVICE_RUNNING);
+    while (status.dwCurrentState == SERVICE_RUNNING)
+    {
+        DWORD dwEvent = WaitForMultipleObjects(2, testevent, FALSE, 1000);
+        switch (dwEvent) 
+        { 
+        //stopServiceEvent, exit while loop
+        case WAIT_OBJECT_0 + 0: 
+            setStatus(SERVICE_STOP_PENDING);
+            break; 
+
+        //cad request
+        case WAIT_OBJECT_0 + 1:
+            if (SendSas.isValid())
+                (*SendSas)(FALSE);
+            break; 
+
+        case WAIT_TIMEOUT:
+            {
+                ConsoleSessionId dwSessionId;
+                if (OlddwSessionId.id != dwSessionId.id)
+                {
+                    OlddwSessionId.id = dwSessionId.id;
+                    SetEvent(sessionEvent);
+                }
+                DWORD dwExitCode = 0;
+                if (hProcess == NULL ||
+                    (GetExitCodeProcess(hProcess, &dwExitCode) &&
+                    dwExitCode != STILL_ACTIVE &&
+                    CloseHandle(hProcess)))
+                {
+                    hProcess = LaunchProcessWin(dwSessionId.id);
+                }
+            }
+            break;
+        }
+    }
+
+    SetEvent(sessionEvent);
+
+    if (hProcess)
+    {
+        WaitForSingleObject(hProcess, 15000);
+        CloseHandle(hProcess);
+    }
+    return 0;
 }
 
 void VNCServerService::stop() {
-  server.stop();
+  SetEvent(stopServiceEvent);
+  SetEvent(sessionEvent);
 }