From 60c419320d2229ace017d1f599b6ab0a7b9e2927 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Fri, 7 Feb 2014 14:53:24 +0000 Subject: [PATCH] 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 --- release/tigervnc.iss.in | 109 ++++++++++++++++++++++++ win/rfb_win32/LaunchProcess.cxx | 10 +++ win/rfb_win32/Service.cxx | 10 ++- win/winvnc/STrayIcon.cxx | 17 +--- win/winvnc/VNCServerService.cxx | 144 ++++++++++++++++++++++++++++++-- win/winvnc/VNCServerService.h | 8 +- win/winvnc/VNCServerWin32.cxx | 6 ++ win/winvnc/VNCServerWin32.h | 1 + win/winvnc/winvnc.cxx | 22 ++--- 9 files changed, 289 insertions(+), 38 deletions(-) diff --git a/release/tigervnc.iss.in b/release/tigervnc.iss.in index 19768407..a27b971f 100644 --- a/release/tigervnc.iss.in +++ b/release/tigervnc.iss.in @@ -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 diff --git a/win/rfb_win32/LaunchProcess.cxx b/win/rfb_win32/LaunchProcess.cxx index 56a712e6..16ced64b 100644 --- a/win/rfb_win32/LaunchProcess.cxx +++ b/win/rfb_win32/LaunchProcess.cxx @@ -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; diff --git a/win/rfb_win32/Service.cxx b/win/rfb_win32/Service.cxx index 982a5b36..89be92e1 100644 --- a/win/rfb_win32/Service.cxx +++ b/win/rfb_win32/Service.cxx @@ -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; } diff --git a/win/winvnc/STrayIcon.cxx b/win/winvnc/STrayIcon.cxx index 354575ad..84575bd0 100644 --- a/win/winvnc/STrayIcon.cxx +++ b/win/winvnc/STrayIcon.cxx @@ -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) { diff --git a/win/winvnc/VNCServerService.cxx b/win/winvnc/VNCServerService.cxx index 2ef2ee08..1a2a8e93 100644 --- a/win/winvnc/VNCServerService.cxx +++ b/win/winvnc/VNCServerService.cxx @@ -20,6 +20,10 @@ #include #include +#include +#include +#include +#include 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); } diff --git a/win/winvnc/VNCServerService.h b/win/winvnc/VNCServerService.h index c7a76cce..a55a7294 100644 --- a/win/winvnc/VNCServerService.h +++ b/win/winvnc/VNCServerService.h @@ -21,19 +21,23 @@ #include #include +#include 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 SendSas; + rfb::win32::Handle stopServiceEvent; + rfb::win32::Handle sessionEvent; + rfb::win32::Handle sessionEventCad; }; }; diff --git a/win/winvnc/VNCServerWin32.cxx b/win/winvnc/VNCServerWin32.cxx index 4d89a0ef..9d77c27c 100644 --- a/win/winvnc/VNCServerWin32.cxx +++ b/win/winvnc/VNCServerWin32.cxx @@ -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(); } } diff --git a/win/winvnc/VNCServerWin32.h b/win/winvnc/VNCServerWin32.h index 5b40a5ab..1feae368 100644 --- a/win/winvnc/VNCServerWin32.h +++ b/win/winvnc/VNCServerWin32.h @@ -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; diff --git a/win/winvnc/winvnc.cxx b/win/winvnc/winvnc.cxx index 1df0f769..4aa5dcb6 100644 --- a/win/winvnc/winvnc.cxx +++ b/win/winvnc/winvnc.cxx @@ -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"); -- 2.39.5