//  Copyright (C) 2002 RealVNC Ltd. All Rights Reserved.
//  Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//  Copyright (C) 2009 GlavSoft LLC. All Rights Reserved.
//
//  This file is part of the VNC system.
//
//  The VNC system is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
//  USA.
//
// TightVNC distribution homepage on the Web: http://www.tightvnc.com/
//
// If the source code for the VNC system is not available from the place 
// whence you received this file, check http://www.uk.research.att.com/vnc or contact
// the authors on vnc@uk.research.att.com for information on obtaining it.


// vncService

// Implementation of service-oriented functionality of WinVNC

#include "stdhdrs.h"

// Header

#include "vncService.h"

#include <lmcons.h>
#include "omnithread.h"
#include "vncMenu.h"
#include "vncTimedMsgBox.h"

// Error message logging
void LogErrorMsg(char *message);

// OS-SPECIFIC ROUTINES

// Create an instance of the vncService class to cause the static fields to be
// initialised properly

vncService init;

DWORD	g_platform_id;
BOOL	g_impersonating_user = FALSE;
HANDLE	g_impersonation_token = 0;
DWORD	g_version_major;
DWORD	g_version_minor;

#ifdef HORIZONLIVE
BOOL	g_nosettings_flag;
#endif

vncService::vncService()
{
    OSVERSIONINFO osversioninfo;
    osversioninfo.dwOSVersionInfoSize = sizeof(osversioninfo);

    // Get the current OS version
    if (!GetVersionEx(&osversioninfo))
	    g_platform_id = 0;
    g_platform_id = osversioninfo.dwPlatformId;
	g_version_major = osversioninfo.dwMajorVersion;
	g_version_minor = osversioninfo.dwMinorVersion;
#ifdef HORIZONLIVE
	g_nosettings_flag = false;
#endif

}

vncService::~vncService()
{
	if (g_impersonating_user) {
		g_impersonating_user = FALSE;
		CloseHandle(g_impersonation_token);
		g_impersonation_token = 0;
	}
}

#ifdef HORIZONLIVE
void
vncService::SetNoSettings(bool flag)
{
	g_nosettings_flag = flag;
}

BOOL vncService::GetNoSettings()
{
	return g_nosettings_flag;
}

#endif


// GetCurrentUser - fills a buffer with the name of the current user!
BOOL
vncService::GetCurrentUser(char *buffer, UINT size)
{
	// How to obtain the name of the current user depends upon the OS being used
	if ((g_platform_id == VER_PLATFORM_WIN32_NT) && vncService::RunningAsService())
	{
		// Windows NT, service-mode

		// -=- FIRSTLY - verify that a user is logged on

		// Get the current Window station
		HWINSTA station = GetProcessWindowStation();
		if (station == NULL)
			return FALSE;

		// Get the current user SID size
		DWORD usersize;
		GetUserObjectInformation(station,
			UOI_USER_SID, NULL, 0, &usersize);

		// Check the required buffer size isn't zero
		if (usersize == 0)
		{
			// No user is logged in - ensure we're not impersonating anyone
			RevertToSelf();
			g_impersonating_user = FALSE;
			CloseHandle(g_impersonation_token);
			g_impersonation_token = 0;

			// Return "" as the name...
			if (strlen("") >= size)
				return FALSE;
			strcpy(buffer, "");

			return TRUE;
		}

		// -=- SECONDLY - a user is logged on but if we're not impersonating
		//     them then we can't continue!
		if (!g_impersonating_user) {
			// Return "" as the name...
			if (strlen("") >= size)
				return FALSE;
			strcpy(buffer, "");
			return TRUE;
		}
	}
		
	// -=- When we reach here, we're either running under Win9x, or we're running
	//     under NT as an application or as a service impersonating a user
	// Either way, we should find a suitable user name.

	switch (g_platform_id)
	{

	case VER_PLATFORM_WIN32_WINDOWS:
	case VER_PLATFORM_WIN32_NT:
		{
			// Just call GetCurrentUser
			DWORD length = size;

			if (GetUserName(buffer, &length) == 0)
			{
				UINT error = GetLastError();

				if (error == ERROR_NOT_LOGGED_ON)
				{
					// No user logged on
					if (strlen("") >= size)
						return FALSE;
					strcpy(buffer, "");
					return TRUE;
				}
				else
				{
					// Genuine error...

					return FALSE;
				}
			}
		}
		return TRUE;
	};

	// OS was not recognised!
	return FALSE;
}

BOOL
vncService::CurrentUser(char *buffer, UINT size)
{
  BOOL result = GetCurrentUser(buffer, size);
  if (result && (strcmp(buffer, "") == 0) && !vncService::RunningAsService()) {
    strncpy(buffer, "Default", size);
  }
  return result;
}

// IsWin95 - returns a BOOL indicating whether the current OS is Win95
BOOL
vncService::IsWin95()
{
	return (g_platform_id == VER_PLATFORM_WIN32_WINDOWS);
}

// IsWinNT - returns a bool indicating whether the current OS is WinNT
BOOL
vncService::IsWinNT()
{
	return (g_platform_id == VER_PLATFORM_WIN32_NT);
}

// Version info
DWORD
vncService::VersionMajor()
{
	return g_version_major;
}

DWORD
vncService::VersionMinor()
{
	return g_version_minor;
}

// Internal routine to find the WinVNC menu class window and
// post a message to it!

BOOL
PostToWinVNC(UINT message, WPARAM wParam, LPARAM lParam)
{
	// Locate the hidden WinVNC menu window
	/*HWND hservwnd = FindWindow(MENU_CLASS_NAME, NULL);
	if (hservwnd == NULL)
		return FALSE;

	// Post the message to WinVNC
	PostMessage(hservwnd, message, wParam, lParam);*/
	return TRUE;
}

// Static routines only used on Windows NT to ensure we're in the right desktop
// These routines are generally available to any thread at any time.

// - SelectDesktop(HDESK)
// Switches the current thread into a different desktop by desktop handle
// This call takes care of all the evil memory management involved

BOOL
vncService::SelectHDESK(HDESK new_desktop)
{
	// Are we running on NT?
	if (IsWinNT())
	{
		HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());

		DWORD dummy;
		char new_name[256];

		if (!GetUserObjectInformation(new_desktop, UOI_NAME, &new_name, 256, &dummy)) {

			return FALSE;
		}



		// Switch the desktop
		if(!SetThreadDesktop(new_desktop)) {

			return FALSE;
		}

		// Switched successfully - destroy the old desktop
		CloseDesktop(old_desktop);

		return TRUE;
	}

	return TRUE;
}

// - SelectDesktop(char *)
// Switches the current thread into a different desktop, by name
// Calling with a valid desktop name will place the thread in that desktop.
// Calling with a NULL name will place the thread in the current input desktop.

extern HDESK vncdll_getinputdesktop( BOOL bSwitchStation );

BOOL
vncService::SelectDesktop(char *name)
{
	// Are we running on NT?
	if (IsWinNT())
	{
		HDESK desktop = vncdll_getinputdesktop( FALSE );

		/*
		if (name != NULL)
		{
			// Attempt to open the named desktop
			desktop = OpenDesktop(name, 0, FALSE,
				DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
				DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
				DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
				DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
		}
		else
		{
			// No, so open the input desktop
			desktop = OpenInputDesktop(0, FALSE,
				DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
				DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
				DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
				DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
		}*/

		// Did we succeed?
		if (desktop == NULL) {

			return FALSE;
		}

		// Switch to the new desktop
		if (!SelectHDESK(desktop)) {
			// Failed to enter the new desktop, so free it!

			CloseDesktop(desktop);

			return FALSE;
		}

		// We successfully switched desktops!
		return TRUE;
	}

	return (name == NULL);
}

// NT only function to establish whether we're on the current input desktop
BOOL vncService::InputDesktopSelected()
{
	vncService::SelectDesktop(NULL);
	return TRUE;
}

// Static routine used to fool Winlogon into thinking CtrlAltDel was pressed

void *
SimulateCtrlAltDelThreadFn(void *context)
{
	HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());

	// Switch into the Winlogon desktop
	if (!vncService::SelectDesktop("Winlogon"))
	{
		return FALSE;
	}

	HWND hwndCtrlAltDel = FindWindow("SAS window class", "SAS window");
	if (hwndCtrlAltDel == NULL) {
		hwndCtrlAltDel = HWND_BROADCAST;
	}

	PostMessage(hwndCtrlAltDel, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));

	// Switch back to our original desktop
	if (old_desktop != NULL)
		vncService::SelectHDESK(old_desktop);

	return NULL;
}

// Static routine to simulate Ctrl-Alt-Del locally

BOOL
vncService::SimulateCtrlAltDel()
{
	// Are we running on NT?
	if (IsWinNT())
	{
		// We simulate Ctrl+Alt+Del by posting a WM_HOTKEY message to the
		// "SAS window" on the Winlogon desktop.
		// This requires that the current thread is part of the Winlogon desktop.
		// But the current thread has hooks set & a window open, so it can't
		// switch desktops, so I instead spawn a new thread & let that do the work...

		omni_thread *thread = omni_thread::create(SimulateCtrlAltDelThreadFn);
		if (thread == NULL)
			return FALSE;
		thread->join(NULL);

		return TRUE;
	}

	return TRUE;
}

// Static routine to lock a 2K or above workstation

BOOL
vncService::LockWorkstation()
{
	if (!IsWinNT()) {

		return FALSE;
	}


	// Load the user32 library
	HMODULE user32 = LoadLibrary("user32.dll");
	if (!user32) {

		return FALSE;
	}

	// Get the LockWorkstation function
	typedef BOOL (*LWProc) ();
	LWProc lockworkstation = (LWProc)GetProcAddress(user32, "LockWorkStation");
	if (!lockworkstation) {
		
		FreeLibrary(user32);
		return FALSE;
	}
	
	// Attempt to lock the workstation
	BOOL result = (lockworkstation)();

	if (!result) {
		FreeLibrary(user32);
		return FALSE;
	}

	FreeLibrary(user32);
	return result;
}

// Static routine to show the Properties dialog for a currently-running
// copy of WinVNC, (usually a servicified version.)

BOOL
vncService::ShowProperties()
{

	return TRUE;
}

// Static routine to find a window by its title substring (case-insensitive).
// Window titles and search substrings will be truncated at length 255.

HWND
vncService::FindWindowByTitle(char *substr)
{
	char l_substr[256];
	strncpy(l_substr, substr, 255);
	l_substr[255] = 0;
	int i;
	for (i = 0; i < (int)strlen(substr); i++) {
		l_substr[i] = tolower(l_substr[i]);
	}

	char title[256];
	HWND hWindow = GetForegroundWindow();
	while (hWindow != NULL) {
		int len = GetWindowText(hWindow, title, 256);
		for (i = 0; i < len; i++) {
			title[i] = tolower(title[i]);
		}
		DWORD style = GetWindowLong(hWindow, GWL_STYLE);
		if ((style & WS_VISIBLE) != 0 && strstr(title, l_substr) != NULL) {
			if (IsIconic(hWindow))
				SendMessage(hWindow, WM_SYSCOMMAND, SC_RESTORE, 0);
			SetForegroundWindow(hWindow);
			break;
		}
		hWindow = GetNextWindow(hWindow, GW_HWNDNEXT);
	}

	return hWindow;
}

BOOL
vncService::PostShareAll()
{
	return TRUE;
}

BOOL
vncService::PostSharePrimary()
{
	return TRUE;
}

BOOL
vncService::PostShareArea(unsigned short x, unsigned short y,
						  unsigned short w, unsigned short h)
{

	return TRUE;
}

BOOL
vncService::PostShareWindow(HWND hwnd)
{
	return TRUE;
}

// Static routine to show the Default Properties dialog for a currently-running
// copy of WinVNC, (usually a servicified version.)

BOOL
vncService::ShowDefaultProperties()
{
	return TRUE;
}

// Static routine to show the About dialog for a currently-running
// copy of WinVNC, (usually a servicified version.)

BOOL
vncService::ShowAboutBox()
{
	return TRUE;
}

// Static routine to tell a locally-running instance of the server
// to connect out to a new client

BOOL
vncService::PostAddNewClient(unsigned long ipaddress, unsigned short port)
{
	return TRUE;
}

// Static routine to tell a locally-running instance of the server
// to disconnect all connected clients.

BOOL
vncService::KillAllClients()
{
	return TRUE;
}

BOOL
vncService::RunningAsService()
{
	return FALSE;
}

BOOL
vncService::KillRunningCopy()
{
	return TRUE;
}


// ROUTINE TO POST THE HANDLE OF THE CURRENT USER TO THE RUNNING WINVNC, IN ORDER
// THAT IT CAN LOAD THE APPROPRIATE SETTINGS.  THIS IS USED ONLY BY THE SVCHELPER
// OPTION, WHEN RUNNING UNDER NT
BOOL
vncService::PostUserHelperMessage()
{
	return TRUE;
}

BOOL
vncService::PostReloadMessage()
{
	return TRUE;
}


// ROUTINE TO PROCESS AN INCOMING INSTANCE OF THE ABOVE MESSAGE
BOOL
vncService::ProcessUserHelperMessage(DWORD processId) {
	// - Check the platform type
	if (!IsWinNT() || !vncService::RunningAsService())
		return TRUE;

	// - Close the HKEY_CURRENT_USER key, to force NT to reload it for the new user
	// NB: Note that this is _really_ dodgy if ANY other thread is accessing the key!
	if (RegCloseKey(HKEY_CURRENT_USER) != ERROR_SUCCESS) {
		return FALSE;
	}

	// - Revert to our own identity
	RevertToSelf();
	g_impersonating_user = FALSE;
	CloseHandle(g_impersonation_token);
	g_impersonation_token = 0;

	// - Open the specified process
	HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
	if (processHandle == NULL) {

		return FALSE;
	}

	// - Get the token for the given process
	HANDLE userToken = NULL;
	if (!OpenProcessToken(processHandle, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &userToken)) {

		CloseHandle(processHandle);
		return FALSE;
	}
	CloseHandle(processHandle);

	// - Set this thread to impersonate them
	if (!ImpersonateLoggedOnUser(userToken)) {

		CloseHandle(userToken);
		return FALSE;
	}

	g_impersonating_user = TRUE;
	g_impersonation_token = userToken;

	return TRUE;
}

bool vncService::tryImpersonate()
{
	if (!IsWinNT() || !vncService::RunningAsService())
		return true;

	if (!g_impersonating_user) {

		return false;
	}
	if (!ImpersonateLoggedOnUser(g_impersonation_token)) {

		return false;
	}

	return true;
}

void vncService::undoImpersonate()
{
	RevertToSelf();
}
