Browse Source

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
tags/v1.3.90
Samuel Mannehed 10 years ago
parent
commit
60c419320d

+ 109
- 0
release/tigervnc.iss.in View 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

+ 10
- 0
win/rfb_win32/LaunchProcess.cxx View 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;

+ 9
- 1
win/rfb_win32/Service.cxx View 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;
}

+ 2
- 15
win/winvnc/STrayIcon.cxx View 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) {

+ 135
- 9
win/winvnc/VNCServerService.cxx View File

@@ -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);
}

+ 6
- 2
win/winvnc/VNCServerService.h View File

@@ -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;
};

};

+ 6
- 0
win/winvnc/VNCServerWin32.cxx View 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();
}
}


+ 1
- 0
win/winvnc/VNCServerWin32.h View 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;

+ 11
- 11
win/winvnc/winvnc.cxx View 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");

Loading…
Cancel
Save