]> source.dussan.org Git - tigervnc.git/commitdiff
Make WinVNC service mode work on Windows Vista and beyond.
authorSamuel Mannehed <samuel@cendio.se>
Fri, 7 Feb 2014 14:53:24 +0000 (14:53 +0000)
committerSamuel Mannehed <samuel@cendio.se>
Fri, 7 Feb 2014 14:53:24 +0000 (14:53 +0000)
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

release/tigervnc.iss.in
win/rfb_win32/LaunchProcess.cxx
win/rfb_win32/Service.cxx
win/winvnc/STrayIcon.cxx
win/winvnc/VNCServerService.cxx
win/winvnc/VNCServerService.h
win/winvnc/VNCServerWin32.cxx
win/winvnc/VNCServerWin32.h
win/winvnc/winvnc.cxx

index 1976840789a0f4e77a995fa634422417c8be6188..a27b971f39640b29a100b75cd56630b1f5e4aacd 100644 (file)
@@ -56,3 +56,112 @@ Name: startservice; Description: "&Start or restart TigerVNC service"; GroupDesc
 Filename: "{app}\winvnc4.exe"; Parameters: "-register"; Tasks: installservice
 Filename: "net"; Parameters: "start winvnc4"; Tasks: startservice
 #endif
+
+#ifdef BUILD_WINVNC
+[Code]
+
+{--- IShellLink ---}
+
+const
+  CLSID_ShellLink = '{00021401-0000-0000-C000-000000000046}';
+  SLDF_RUNAS_USER = $2000;
+
+type
+  IShellLinkW = interface(IUnknown)
+    '{000214F9-0000-0000-C000-000000000046}'
+    procedure Dummy;
+    procedure Dummy2;
+    procedure Dummy3;
+    function GetDescription(pszName: String; cchMaxName: Integer): HResult;
+    function SetDescription(pszName: String): HResult;
+    function GetWorkingDirectory(pszDir: String; cchMaxPath: Integer): HResult;
+    function SetWorkingDirectory(pszDir: String): HResult;
+    function GetArguments(pszArgs: String; cchMaxPath: Integer): HResult;
+    function SetArguments(pszArgs: String): HResult;
+    function GetHotkey(var pwHotkey: Word): HResult;
+    function SetHotkey(wHotkey: Word): HResult;
+    function GetShowCmd(out piShowCmd: Integer): HResult;
+    function SetShowCmd(iShowCmd: Integer): HResult;
+    function GetIconLocation(pszIconPath: String; cchIconPath: Integer;
+      out piIcon: Integer): HResult;
+    function SetIconLocation(pszIconPath: String; iIcon: Integer): HResult;
+    function SetRelativePath(pszPathRel: String; dwReserved: DWORD): HResult;
+    function Resolve(Wnd: HWND; fFlags: DWORD): HResult;
+    function SetPath(pszFile: String): HResult;
+  end;
+
+  IShellLinkDataList = interface(IUnknown)
+    '{45E2B4AE-B1C3-11D0-B92F-00A0C90312E1}'
+    function AddDataBlock(pDataBlock : DWORD) : HResult;
+    function CopyDataBlock(dwSig : DWORD; var ppDataBlock : DWORD) : HResult;
+    function RemoveDataBlock(dwSig : DWORD) : HResult;
+    function GetFlags(var pdwFlags : DWORD) : HResult;
+    function SetFlags(dwFlags : DWORD) : HResult;
+  end;
+
+  IPersist = interface(IUnknown)
+    '{0000010C-0000-0000-C000-000000000046}'
+    function GetClassID(var classID: TGUID): HResult;
+  end;
+
+  IPersistFile = interface(IPersist)
+    '{0000010B-0000-0000-C000-000000000046}'
+    function IsDirty: HResult;
+    function Load(pszFileName: String; dwMode: Longint): HResult;
+    function Save(pszFileName: String; fRemember: BOOL): HResult;
+    function SaveCompleted(pszFileName: String): HResult;
+    function GetCurFile(out pszFileName: String): HResult;
+  end;
+
+var
+  OSVersion: TWindowsVersion;
+
+function InitializeSetup: Boolean;
+begin
+  GetWindowsVersionEx(OSVersion);
+  Result := True;
+end;
+
+procedure SetRunAsUserFlag(Path: String);
+var
+  Obj: IUnknown;
+  SL: IShellLinkW;
+  SDL: IShellLinkDataList;
+  PF: IPersistFile;
+  Flags: DWord;
+begin
+  Obj := CreateComObject(StringToGuid(CLSID_ShellLink));
+  SL := IShellLinkW(Obj);
+  PF := IPersistFile(Obj);
+  SDL := IShellLinkDataList(Obj);
+  Path := ExpandConstant(Path);
+  OleCheck(PF.Load(Path, 0));
+  OleCheck(SDL.GetFlags(Flags));
+  OleCheck(SDL.SetFlags(Flags or SLDF_RUNAS_USER));
+  OleCheck(PF.Save(Path, True));
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+var
+  Flags: DWord;
+begin
+  { Post-install actions on Windows Vista and higher:
+    o Modify Service-Mode start menu commands so they run as administrator.
+    o Set up the SoftwareSASGeneration system policy so as to allow services to simulate Ctrl+Alt+Del. }
+  if (CurStep = ssPostInstall) and (OSVersion.Major >= 6) then begin
+    SetRunAsUserFlag('{group}\VNC Server (Service-Mode)\Configure VNC Service.lnk');
+    SetRunAsUserFlag('{group}\VNC Server (Service-Mode)\Register VNC Service.lnk');
+    SetRunAsUserFlag('{group}\VNC Server (Service-Mode)\Unregister VNC Service.lnk');
+    SetRunAsUserFlag('{group}\VNC Server (Service-Mode)\Start VNC Service.lnk');
+    SetRunAsUserFlag('{group}\VNC Server (Service-Mode)\Stop VNC Service.lnk');
+    if not RegQueryDWordValue(
+      HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
+      'SoftwareSASGeneration', Flags
+    ) then Flags := 0;
+    RegWriteDWordValue(
+      HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
+      'SoftwareSASGeneration', Flags or 1
+    );
+  end;
+end;  
+#endif
index 56a712e6e9f9a9fda288ca92cab8f74ceeb4c5cf..16ced64ba663208c224f1fa4b2f3f8d05f9325ba 100644 (file)
@@ -44,10 +44,20 @@ void LaunchProcess::start(HANDLE userToken, bool createConsole) {
   await();
   returnCode = STILL_ACTIVE;
 
+  DWORD size;
+  char desktopName[256];
+  char buf[256];
+  HDESK desktop = GetThreadDesktop(GetCurrentThreadId());
+  if (!GetUserObjectInformation(desktop, UOI_NAME, buf, 256, &size))
+    throw rdr::SystemException("unable to launch process", GetLastError());
+
+  snprintf(desktopName, 256, "WinSta0\\%s", buf);
+
   // - Create storage for the process startup information
   STARTUPINFO sinfo;
   memset(&sinfo, 0, sizeof(sinfo));
   sinfo.cb = sizeof(sinfo);
+  sinfo.lpDesktop = desktopName;
 
   // - Concoct a suitable command-line
   TCharArray exePath;
index 982a5b3645cfdf2b125372cc84b5092eadaaec1e..89be92e1d7eef4d00759883fae042d3ac60c9d4d 100644 (file)
@@ -40,6 +40,7 @@ static LogWriter vlog("Service");
 // - Internal service implementation functions
 
 Service* service = 0;
+bool runAsService = false;
 
 VOID WINAPI serviceHandler(DWORD control) {
   switch (control) {
@@ -323,6 +324,13 @@ rfb::win32::emulateCtrlAltDel() {
   if (!osVersion.isPlatformNT)
     return false;
 
+  if (osVersion.dwMajorVersion >= 6) {
+    rfb::win32::Handle sessionEventCad = 
+      CreateEvent(0, FALSE, FALSE, "Global\\SessionEventTigerVNCCad");
+    SetEvent(sessionEventCad);
+    return true;
+  }
+
   CADThread* cad_thread = new CADThread();
   vlog.debug("emulate Ctrl-Alt-Del");
   if (cad_thread) {
@@ -641,5 +649,5 @@ char* rfb::win32::serviceStateName(DWORD state) {
 
 
 bool rfb::win32::isServiceProcess() {
-  return service != 0;
+  return runAsService;
 }
index 354575ad8548d3191ec8fdd766b51071aae30708..84575bd06b32142a35bacac92fb9c717fad313ad 100644 (file)
@@ -136,22 +136,10 @@ public:
         }
         break;
       case ID_OPTIONS:
-        {
-          CurrentUserToken token;
-          if (token.canImpersonate())
-            vncConfig.start(isServiceProcess() ? (HANDLE)token : INVALID_HANDLE_VALUE);
-          else
-            vlog.error("Options: unknown current user");
-        }
+        vncConfig.start(INVALID_HANDLE_VALUE);
         break;
       case ID_CONNECT:
-        {
-          CurrentUserToken token;
-          if (token.canImpersonate())
-            vncConnect.start(isServiceProcess() ? (HANDLE)token : INVALID_HANDLE_VALUE);
-          else
-            vlog.error("Options: unknown current user");
-        }
+        vncConnect.start(INVALID_HANDLE_VALUE);
         break;
       case ID_DISCONNECT:
         thread.server.disconnectClients("tray menu disconnect");
@@ -160,7 +148,6 @@ public:
         if (MsgBox(0, _T("Are you sure you want to close the server?"),
                    MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) == IDYES) {
           if (isServiceProcess()) {
-            ImpersonateCurrentUser icu;
             try {
               rfb::win32::stopService(VNCServerService::Name);
             } catch (rdr::Exception& e) {
index 2ef2ee080ba827c9f8209db73a762027633b9f71..1a2a8e93aaac3aa5bf469d4786ce394e0f8a48eb 100644 (file)
 
 #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 @@ using namespace win32;
 
 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 @@ VNCServerService::VNCServerService(VNCServerWin32& s)
 }
 
 
-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);
 }
index c7a76cce459e5532ae644de33f9610383caf38d0..a55a729442dd8d37621baa9028a2a8d0520b8b4b 100644 (file)
 
 #include <winvnc/VNCServerWin32.h>
 #include <rfb_win32/Service.h>
+#include <rfb_win32/DynamicFn.h>
 
 namespace winvnc {
 
   class VNCServerService : public rfb::win32::Service {
   public:
-    VNCServerService(VNCServerWin32& s);
+    VNCServerService();
 
     DWORD serviceMain(int argc, TCHAR* argv[]);
     void stop();
 
     static const TCHAR* Name;
   protected:
-    VNCServerWin32& server;
+    rfb::win32::DynamicFn<void (WINAPI *)(BOOL)> SendSas;
+    rfb::win32::Handle stopServiceEvent;
+    rfb::win32::Handle sessionEvent;
+    rfb::win32::Handle sessionEventCad;
   };
 
 };
index 4d89a0efe01094ceafef77218ca363c328523b12..9d77c27ce456443d5ac71ad1cb94537ada2d3871 100644 (file)
@@ -55,6 +55,8 @@ static BoolParameter showTrayIcon("ShowTrayIcon",
 VNCServerWin32::VNCServerWin32()
   : command(NoCommand), commandSig(commandLock),
     commandEvent(CreateEvent(0, TRUE, FALSE, 0)),
+    sessionEvent(isServiceProcess() ?
+      CreateEvent(0, FALSE, FALSE, "Global\\SessionEventTigerVNC") : 0),
     vncServer(CStr(ComputerName().buf), &desktop),
     hostThread(0), runServer(false), isDesktopStarted(false),
     httpServer(&vncServer), config(&sockMgr), trayIcon(0),
@@ -72,6 +74,8 @@ VNCServerWin32::VNCServerWin32()
 
   // Register the queued command event to be handled
   sockMgr.addEvent(commandEvent, this);
+  if (sessionEvent)
+    sockMgr.addEvent(sessionEvent, this);
 }
 
 VNCServerWin32::~VNCServerWin32() {
@@ -322,6 +326,8 @@ void VNCServerWin32::processEvent(HANDLE event_) {
       command = NoCommand;
       commandSig.signal();
     }
+  } else if (event_ == sessionEvent.h) {
+    stop();
   }
 }
 
index 5b40a5ab365a8b5157b42994be1b15978a284800..1feae3680da79ed5acd0b5a25c33aac00ea867e6 100644 (file)
@@ -102,6 +102,7 @@ namespace winvnc {
     rfb::Mutex commandLock;
     rfb::Condition commandSig;
     rfb::win32::Handle commandEvent;
+    rfb::win32::Handle sessionEvent;
 
     // VNCServerWin32 Server-internal state
     rfb::win32::SDisplay desktop;
index 1df0f769b97d2f32e1184e47b58b01559903cab0..4aa5dcb665d4fa1354b4ef84ff024c6a6f960421 100644 (file)
@@ -43,7 +43,7 @@ static LogWriter vlog("main");
 TStr rfb::win32::AppName("VNC Server");
 
 
-static bool runAsService = false;
+extern bool runAsService;
 static bool runServer = true;
 static bool close_console = false;
 
@@ -158,6 +158,11 @@ static void processParams(int argc, char** argv) {
         sprintf(result.buf, stateMsg, (const char*)CStr(VNCServerService::Name), stateStr.buf);
         MsgBoxOrLog(result.buf);
       } else if (strcasecmp(argv[i], "-service") == 0) {
+        printf("Run in service mode\n");
+        runServer = false;
+        runAsService = true;
+
+      } else if (strcasecmp(argv[i], "-service_run") == 0) {
         printf("Run in service mode\n");
         runAsService = true;
 
@@ -255,16 +260,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prevInst, char* cmdLine, int cmdSho
     if (runServer) {
       // Start the network subsystem and run the server
       VNCServerWin32 server;
-
-      if (runAsService) {
-        printf("Starting Service-Mode VNC Server.\n");
-        VNCServerService service(server);
-        service.start();
-        result = service.getStatus().dwWin32ExitCode;
-      } else {
-        printf("Starting User-Mode VNC Server.\n");
-        result = server.run();
-      }
+      result = server.run();
+    } else if (runAsService) {
+      VNCServerService service;
+      service.start();
+      result = service.getStatus().dwWin32ExitCode;
     }
 
     vlog.debug("WinVNC service destroyed");