From e08d3afdbc05899b0cca1f89a37646ad231fbd7c Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Tue, 11 Aug 2020 12:27:04 +1000 Subject: [PATCH] [host] Windows: added missing service files --- host/platform/Windows/src/service.c | 559 ++++++++++++++++++++++++++++ host/platform/Windows/src/service.h | 3 + 2 files changed, 562 insertions(+) create mode 100644 host/platform/Windows/src/service.c create mode 100644 host/platform/Windows/src/service.h diff --git a/host/platform/Windows/src/service.c b/host/platform/Windows/src/service.c new file mode 100644 index 00000000..a52f8c73 --- /dev/null +++ b/host/platform/Windows/src/service.c @@ -0,0 +1,559 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2020 Geoffrey McRae +https://looking-glass.hostfission.com + +This program 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 +*/ + +#define INSTANCE_MUTEX_NAME "Global\\6f1a5eec-af3f-4a65-99dd-ebe0e4ecea55" + +#include "interface/platform.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define SVCNAME "Looking Glass Host" +#define SVC_ERROR ((DWORD)0xC0020001L) + +struct Service +{ + FILE * logFile; +}; + +struct Service service = { 0 }; + +void doLog(const char * fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(service.logFile, fmt, args); + va_end(args); +} + +static void setupLogging() +{ + char tempPath[MAX_PATH+1]; + GetTempPathA(sizeof(tempPath), tempPath); + int len = snprintf(NULL, 0, "%slooking-glass-host-service.txt", tempPath); + char * logFilePath = malloc(len + 1); + sprintf(logFilePath, "%slooking-glass-host-service.txt", tempPath); + service.logFile = fopen(logFilePath, "a+"); + doLog("Startup\n"); +} + +static void finishLogging() +{ + doLog("Finished\n"); + fclose(service.logFile); +} + +void winerr() +{ + char buf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf, (sizeof(buf) / sizeof(char)), NULL); + doLog("0x%08lx - %s", GetLastError(), buf); +} + +bool enablePriv(const char * name) +{ + HANDLE hToken; + LUID luid; + TOKEN_PRIVILEGES tp = { 0 }; + + if (!OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + doLog("failed to open the process\n"); + winerr(); + return -1; + } + + if (!LookupPrivilegeValueA(NULL, name, &luid)) + { + doLog("failed to lookup the privilege value\n"); + winerr(); + goto fail; + } + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, + NULL)) + { + doLog("failed to adjust the token privilege\n"); + winerr(); + goto fail; + } + + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + doLog("the token doesn't have the specified privilege - %s\n", name); + winerr(); + goto fail; + } + + return true; + +fail: + CloseHandle(hToken); + return false; +} + +HANDLE dupeSystemProcessToken() +{ + DWORD count = 0; + DWORD returned; + do + { + count += 512; + DWORD pids[count]; + EnumProcesses(pids, count * sizeof(DWORD), &returned); + } + while(returned / sizeof(DWORD) == count); + + DWORD pids[count]; + EnumProcesses(pids, count * sizeof(DWORD), &returned); + returned /= sizeof(DWORD); + + for(DWORD i = 0; i < returned; ++i) + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pids[i]); + if (!hProcess) + continue; + + HANDLE hToken; + if (!OpenProcessToken(hProcess, + TOKEN_QUERY | TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_QUERY_SOURCE | + TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_EXECUTE, &hToken)) + goto err_proc; + + DWORD tmp; + char userBuf[1024]; + TOKEN_USER * user = (TOKEN_USER *)userBuf; + if (!GetTokenInformation(hToken, TokenUser, user, sizeof(userBuf), &tmp)) + goto err_token; + + CHAR * sid = NULL; + if (!ConvertSidToStringSidA(user->User.Sid, &sid)) + goto err_token; + + if (strcmp(sid, "S-1-5-18") == 0) + { + LocalFree(sid); + CloseHandle(hProcess); + + // duplicate the token so we can use it + HANDLE hDupe = NULL; + if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, + TokenPrimary, &hDupe)) + hDupe = NULL; + + CloseHandle(hToken); + return hDupe; + } + + LocalFree(sid); +err_token: + CloseHandle(hToken); +err_proc: + CloseHandle(hProcess); + } + + return NULL; +} + +DWORD GetInteractiveSessionID() +{ + PWTS_SESSION_INFO pSessionInfo; + DWORD count; + DWORD ret = 0; + + if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, + &count)) + return 0; + + for(DWORD i = 0; i < count; ++i) + { + if (pSessionInfo[i].State == WTSActive) + { + ret = pSessionInfo[i].SessionId; + break; + } + } + + WTSFreeMemory(pSessionInfo); + return ret; +} + +void Launch() +{ + if (!enablePriv(SE_DEBUG_NAME)) + return; + + HANDLE hToken = dupeSystemProcessToken(); + if (!hToken) + { + doLog("failed to get the system process token\n"); + return; + } + + DWORD origSessionID, targetSessionID, returnedLen; + GetTokenInformation(hToken, TokenSessionId, &origSessionID, + sizeof(origSessionID), &returnedLen); + + if (!enablePriv(SE_TCB_NAME)) + goto fail_token; + + targetSessionID = GetInteractiveSessionID(); + if (origSessionID != targetSessionID) + { + if (!SetTokenInformation(hToken, TokenSessionId, + &targetSessionID, sizeof(targetSessionID))) + { + doLog("failed to set interactive token\n"); + winerr(); + goto fail_token; + } + } + + LPVOID pEnvironment = NULL; + if (!CreateEnvironmentBlock(&pEnvironment, hToken, TRUE)) + { + doLog("fail_tokened to create the envionment block\n"); + winerr(); + goto fail_token; + } + + if (!enablePriv(SE_IMPERSONATE_NAME)) + goto fail_token; + + if (!ImpersonateLoggedOnUser(hToken)) + { + doLog("fail_tokened to impersonate\n"); + winerr(); + goto fail_token; + } + + if (!enablePriv(SE_ASSIGNPRIMARYTOKEN_NAME)) + goto fail_token; + + if (!enablePriv(SE_INCREASE_QUOTA_NAME)) + goto fail_token; + + DWORD flags = CREATE_NEW_CONSOLE; + if (!pEnvironment) + flags |= CREATE_UNICODE_ENVIRONMENT; + + PROCESS_INFORMATION pi = {0}; + STARTUPINFO si = + { + .cb = sizeof(STARTUPINFO), + .dwFlags = STARTF_USESHOWWINDOW, + .wShowWindow = SW_SHOW, + .lpDesktop = "WinSta0\\Default" + }; + + char * exe = strdup(os_getExecutable()); + if (!CreateProcessAsUserA( + hToken, + NULL, + exe, + NULL, + NULL, + TRUE, + flags, + NULL, + os_getDataPath(), + &si, + &pi + )) + { + doLog("failed to launch\n"); + winerr(); + goto fail_exe; + } + +fail_exe: + free(exe); + +fail_token: + CloseHandle(hToken); +} + +VOID SvcReportEvent(LPTSTR szFunction) +{ + HANDLE hEventSource; + LPCTSTR lpszStrings[2]; + TCHAR Buffer[80]; + + hEventSource = RegisterEventSource(NULL, SVCNAME); + + if (hEventSource) + { + snprintf(Buffer, sizeof(Buffer), "%s failed with 0x%lx", szFunction, GetLastError()); + + lpszStrings[0] = SVCNAME; + lpszStrings[1] = Buffer; + + ReportEvent(hEventSource, // event log handle + EVENTLOG_ERROR_TYPE, // event type + 0, // event category + SVC_ERROR, // event identifier + NULL, // no security identifier + 2, // size of lpszStrings array + 0, // no binary data + lpszStrings, // array of strings + NULL); // no binary data + + DeregisterEventSource(hEventSource); + } +} + +void Install() +{ + TCHAR szPath[MAX_PATH]; + + SC_HANDLE schSCManager; + SC_HANDLE schService; + + if (!GetModuleFileName(NULL, szPath, MAX_PATH)) + { + doLog("Cannot install service (0x%lx)\n", GetLastError()); + return; + } + + // Get a handle to the SCM database. + + schSCManager = OpenSCManager( + NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + doLog("OpenSCManager failed (0x%lx)\n", GetLastError()); + return; + } + + // Create the service + + schService = CreateService( + schSCManager, // SCM database + SVCNAME, // name of service + SVCNAME, // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + os_getExecutable(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + doLog("CreateService failed (0x%lx)\n", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } + else + doLog("Service installed successfully\n"); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); +} + +void Uninstall() +{ + SC_HANDLE schSCManager; + SC_HANDLE schService; + + schSCManager = OpenSCManager( + NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + doLog("OpenSCManager failed (0x%lx)\n", GetLastError()); + return; + } + + schService = OpenService(schSCManager, SVCNAME, DELETE); + if (!schService) + { + doLog("OpenService failed (0x%lx)\n", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } + + SERVICE_STATUS status; + if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) + while(status.dwCurrentState != SERVICE_STOPPED) + { + Sleep(1); + QueryServiceStatus(schService, &status); + } + + if (!DeleteService(schService)) + { + doLog("DeleteService failed (0x%lx)\n", GetLastError()); + CloseServiceHandle(schSCManager); + return; + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); +} + +SERVICE_STATUS gSvcStatus; +SERVICE_STATUS_HANDLE gSvcStatusHandle; +HANDLE ghSvcStopEvent = NULL; + +void ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, + DWORD dwWaitHint) +{ + static DWORD dwCheckPoint = 1; + + gSvcStatus.dwCurrentState = dwCurrentState; + gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; + gSvcStatus.dwWaitHint = dwWaitHint; + + if (dwCurrentState == SERVICE_START_PENDING) + gSvcStatus.dwControlsAccepted = 0; + else + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + + if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) + gSvcStatus.dwCheckPoint = 0; + else + gSvcStatus.dwCheckPoint = dwCheckPoint++; + + SetServiceStatus(gSvcStatusHandle, &gSvcStatus); +} + +VOID WINAPI SvcCtrlHandler(DWORD dwControl) +{ + switch(dwControl) + { + case SERVICE_CONTROL_STOP: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + SetEvent(ghSvcStopEvent); + break; + + case SERVICE_CONTROL_INTERROGATE: + break; + + default: + break; + } + + ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); +} + +VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv) +{ + gSvcStatusHandle = RegisterServiceCtrlHandler(SVCNAME, SvcCtrlHandler); + + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 0); + + ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ghSvcStopEvent) + { + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + return; + } + + setupLogging(); + HANDLE m = CreateMutex(NULL, FALSE, INSTANCE_MUTEX_NAME); + + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + while(1) + { + /* check if the app is running by trying to take the lock */ + bool running = true; + if (WaitForSingleObject(m, 0) == WAIT_OBJECT_0) + { + running = false; + ReleaseMutex(m); + } + + if (!running && GetInteractiveSessionID() != 0) + Launch(); + + if (WaitForSingleObject(ghSvcStopEvent, 100) == WAIT_OBJECT_0) + break; + } + + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + CloseHandle(ghSvcStopEvent); + finishLogging(); +} + +bool HandleService(int argc, char * argv[]) +{ + service.logFile = stdout; + if (argc > 1) + { + if (strcmp(argv[1], "InstallService") == 0) + { + Install(); + return true; + } + + if (strcmp(argv[1], "UninstallService") == 0) + { + Uninstall(); + return true; + } + } + + SERVICE_TABLE_ENTRY DispatchTable[] = { + { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, + { NULL, NULL } + }; + + if (StartServiceCtrlDispatcher(DispatchTable)) + return true; + + /* only allow one instance to run */ + HANDLE m = CreateMutex(NULL, FALSE, INSTANCE_MUTEX_NAME); + if (WaitForSingleObject(m, 0) != WAIT_OBJECT_0) + { + CloseHandle(m); + return true; + } + + return false; +} diff --git a/host/platform/Windows/src/service.h b/host/platform/Windows/src/service.h new file mode 100644 index 00000000..28cc780f --- /dev/null +++ b/host/platform/Windows/src/service.h @@ -0,0 +1,3 @@ +#include + +bool HandleService(int argc, char * argv[]);