Patch by Jochen Tucht, fixes bug 135. git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@5158 3789f03b-4d11-0410-bbf8-ca57d06f2519tags/v1.3.90
@@ -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 |
@@ -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; |
@@ -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; | |||
} |
@@ -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) { |
@@ -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 @@ 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); | |||
} |
@@ -21,19 +21,23 @@ | |||
#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; | |||
}; | |||
}; |
@@ -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(); | |||
} | |||
} | |||
@@ -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; |
@@ -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"); |