tunsafe-clang15/service_win32.cpp
Ludvig Strigeus cf92ac7a0c Updates for TunSafe 1.4-rc1
1.Subfolders in the Config/ directory now show up as submenus.
2.Added a way to run TunSafe as a Windows Service.
  Foreground Mode: The service will disconnect when TunSafe closes.
  Background Mode: The service will stay connected in the background.
  No longer required to run the TunSafe client as Admin as long as
  the service is running.
3.New config setting [Interface].ExcludedIPs to configure IPs that
  should not be routed through TunSafe.
4.Can now automatically start TunSafe when Windows starts
5.New UI with tabs and graphs
6.Cache DNS queries to ensure DNS will succeed if connection fails
7.Recreate tray icon when explorer.exe restarts
8.Renamed window title to TunSafe instead of TunSafe VPN Client
9.Main window is now resizable
10.Disallow roaming endpoint when using AllowedIPs=0.0.0.0/0
   Only the original endpoint is added in the routing table so
   this would result in an endless loop of packets.
11.Display approximate Wireguard framing overhead in stats
12.Preparations for protocol handling with multiple threads
13.Delete the routes we made when disconnecting
14.Fix error message about unable to delete a route when connecting
2018-08-12 03:30:06 +02:00

1180 lines
34 KiB
C++

// SPDX-License-Identifier: AGPL-1.0-only
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
#include "stdafx.h"
#include "service_win32.h"
#include <strsafe.h>
#include "util.h"
#include "network_win32_api.h"
#include <algorithm>
#include <string>
#include <assert.h>
#include "util_win32.h"
static const uint64 kTunsafeServiceProtocolVersion = 20180809001;
static SERVICE_STATUS_HANDLE m_statusHandle;
static TunsafeServiceImpl *g_service;
#define SERVICE_NAME L"TunSafeService"
#define SERVICE_NAMEA "TunSafeService"
#define SERVICE_START_TYPE SERVICE_AUTO_START
#define SERVICE_DEPENDENCIES L"tap0901\0dhcp\0"
#define SERVICE_ACCOUNT NULL
//L"NT AUTHORITY\\LocalService"
#define SERVICE_PASSWORD NULL
#define PIPE_NAME "\\\\.\\pipe\\TunSafe\\ServiceControl"
enum {
SERVICE_REQ_LOGIN = 0,
SERVICE_REQ_START = 1,
SERVICE_REQ_STOP = 2,
SERVICE_REQ_GETSTATS = 4,
SERVICE_REQ_SET_INTERNET_BLOCKSTATE = 5,
SERVICE_REQ_RESETSTATS = 6,
SERVICE_REQ_SET_STARTUP_FLAGS = 7,
SERVICE_MSG_STATE = 8,
SERVICE_MSG_LOGLINE = 9,
SERVICE_MSG_STATS = 11,
SERVICE_MSG_CLEARLOG = 12,
SERVICE_MSG_STATUS_CODE = 14,
SERVICE_REQ_GET_GRAPH = 15,
SERVICE_MSG_GRAPH = 16,
};
struct ServiceHandles {
SC_HANDLE manager;
SC_HANDLE service;
ServiceHandles() : manager(NULL), service(NULL) {}
~ServiceHandles() {
if (manager)
CloseServiceHandle(manager);
if (service)
CloseServiceHandle(service);
}
bool Open(PWSTR pszServiceName, DWORD sc_rights, DWORD service_rights);
bool StopService();
bool StartService();
};
static DWORD InstallService(PWSTR pszServiceName,
PWSTR pszDisplayName,
DWORD dwStartType,
PWSTR pszDependencies,
PWSTR pszAccount,
PWSTR pszPassword) {
wchar_t szPath[MAX_PATH + 32];
ServiceHandles handles;
DWORD res;
szPath[0] = '"';
if (GetModuleFileNameW(NULL, szPath + 1, MAX_PATH) == 0) {
res = GetLastError();
goto Cleanup;
}
size_t len = wcslen(szPath);
memcpy(szPath + len, L"\" --service", 12 * sizeof(wchar_t));
// Open the local default service control manager database
handles.manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE);
if (handles.manager == NULL) {
res = GetLastError();
goto Cleanup;
}
// Install the service into SCM by calling CreateService
handles.service = CreateServiceW(
handles.manager, // SCManager database
pszServiceName, // Name of service
pszDisplayName, // Name to display
SERVICE_QUERY_STATUS, // Desired access
SERVICE_WIN32_OWN_PROCESS, // Service type
dwStartType, // Service start type
SERVICE_ERROR_NORMAL, // Error control type
szPath, // Service's binary
NULL, // No load ordering group
NULL, // No tag identifier
pszDependencies, // Dependencies
pszAccount, // Service running account
pszPassword // Password of the account
);
if (handles.service == NULL) {
res = GetLastError();
goto Cleanup;
}
{
SERVICE_DESCRIPTIONA desc;
desc.lpDescription = "TunSafe uses this service to connect to a VPN server in the background.";
ChangeServiceConfig2A(handles.service, SERVICE_CONFIG_DESCRIPTION, &desc);
}
res = 0;
Cleanup:
if (res && res != ERROR_SERVICE_EXISTS)
RERROR("TunSafe service installation failed: %d", res);
return res;
}
bool ServiceHandles::Open(PWSTR pszServiceName, DWORD sc_rights, DWORD service_rights) {
manager = OpenSCManagerW(NULL, NULL, sc_rights);
if (manager == NULL)
return false;
service = OpenServiceW(manager, pszServiceName, service_rights);
return (service != NULL);
}
bool ServiceHandles::StopService() {
SERVICE_STATUS ssSvcStatus = {};
// Try to stop the service
if (ControlService(service, SERVICE_CONTROL_STOP, &ssSvcStatus)) {
Sleep(100);
while (QueryServiceStatus(service, &ssSvcStatus)) {
if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING) {
Sleep(100);
} else {
break;
}
}
}
return (ssSvcStatus.dwCurrentState == SERVICE_STOPPED);
}
static wchar_t *GetUsernameOfCurrentUser(bool use_thread_token) {
HANDLE thread_token = NULL;
wchar_t *result = NULL;
DWORD len;
PTOKEN_USER token_user = NULL;
DWORD domain_len;
WCHAR username[256], domain[256];
SID_NAME_USE sid_type;
if (use_thread_token) {
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &thread_token))
goto getout;
} else {
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &thread_token))
goto getout;
}
len = 0;
token_user = NULL;
while (!GetTokenInformation(thread_token, TokenUser, token_user, len, &len)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto getout;
token_user = (PTOKEN_USER)realloc(token_user, len);
if (!token_user)
goto getout;
}
if (!IsValidSid(token_user->User.Sid))
goto getout;
domain_len = len = 256;
if (!LookupAccountSidW(NULL, token_user->User.Sid, username, &len, domain, &domain_len, &sid_type))
goto getout;
size_t alen = wcslen(username);
size_t blen = wcslen(domain);
result = (wchar_t*)malloc((alen + blen + 2) * sizeof(wchar_t));
if (result) {
result[alen] = '@';
memcpy(result, username, alen * sizeof(wchar_t));
memcpy(result + alen + 1, domain, (blen + 1) * sizeof(wchar_t));
}
getout:
free(token_user);
CloseHandle(thread_token);
return result;
}
static DWORD GetNonTransientServiceStatus(SC_HANDLE service) {
SERVICE_STATUS ssSvcStatus = {};
int delay = 100;
for(;;) {
if (!QueryServiceStatus(service, &ssSvcStatus))
return 0;
if (--delay == 0 ||
ssSvcStatus.dwCurrentState != SERVICE_START_PENDING &&
ssSvcStatus.dwCurrentState != SERVICE_STOP_PENDING)
return ssSvcStatus.dwCurrentState;
Sleep(100);
delay--;
}
}
bool ServiceHandles::StartService() {
DWORD state = GetNonTransientServiceStatus(service);
if (state == 0 || state == SERVICE_RUNNING)
return false; // service already running, no need to start
if (!::StartService(service, 0, NULL)) {
// if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
// return false;
return false;
}
return GetNonTransientServiceStatus(service) == SERVICE_RUNNING;
}
static bool StartTunsafeService() {
ServiceHandles handles;
if (!handles.Open(SERVICE_NAME, SC_MANAGER_CONNECT, SERVICE_START | SERVICE_QUERY_STATUS))
return false;
return handles.StartService();
}
bool IsTunsafeServiceRunning() {
ServiceHandles handles;
if (!handles.Open(SERVICE_NAME, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS))
return false;
return GetNonTransientServiceStatus(handles.service) == SERVICE_RUNNING;
}
void StopTunsafeService() {
ServiceHandles handles;
if (!handles.Open(SERVICE_NAME, SC_MANAGER_CONNECT,
SERVICE_STOP | SERVICE_QUERY_STATUS))
goto Cleanup;
handles.StopService();
Cleanup:
return;
}
static void SetTunsafeUserNameInRegistry() {
wchar_t *user = GetUsernameOfCurrentUser(false);
if (!user) {
RERROR("Unable to get current username");
return;
}
HKEY hkey = NULL;
RegCreateKeyEx(HKEY_LOCAL_MACHINE, "Software\\TunSafe", NULL, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, NULL);
if (!hkey) {
RERROR("Unable to open registry key");
return;
}
if (RegSetValueExW(hkey, L"AllowedUsername", NULL, REG_SZ, (BYTE*)user, (DWORD)(wcslen(user) + 1) * 2) != ERROR_SUCCESS) {
RERROR("Unable to set registry key");
}
RegCloseKey(hkey);
}
void InstallTunSafeWindowsService() {
InstallService(SERVICE_NAME, L"TunSafe Service", SERVICE_START_TYPE,
SERVICE_DEPENDENCIES, SERVICE_ACCOUNT, SERVICE_PASSWORD);
StartTunsafeService();
SetTunsafeUserNameInRegistry();
}
bool UninstallTunSafeWindowsService() {
ServiceHandles handles;
if (!handles.Open(SERVICE_NAME, SC_MANAGER_CONNECT,
SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE))
goto Cleanup;
handles.StopService();
if (!DeleteService(handles.service))
goto Cleanup;
return true;
Cleanup:
return false;
}
bool IsTunSafeServiceInstalled() {
ServiceHandles handles;
return handles.Open(SERVICE_NAME, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS);
}
static void WriteServiceLog(const char *pszFunction, WORD dwError) {
char szMessage[260];
snprintf(szMessage, ARRAYSIZE(szMessage), "%s failed w/err 0x%08lx", pszFunction, dwError);
HANDLE hEventSource = NULL;
LPCSTR lpszStrings[2] = {NULL, NULL};
hEventSource = RegisterEventSourceW(NULL, SERVICE_NAME);
if (hEventSource) {
lpszStrings[0] = SERVICE_NAMEA;
lpszStrings[1] = szMessage;
ReportEventA(hEventSource, // Event log handle
dwError, // Event type
0, // Event category
0, // 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);
}
}
static void SetServiceStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode = 0,
DWORD dwWaitHint = 0) {
static DWORD dwCheckPoint = 1;
SERVICE_STATUS m_status;
m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
m_status.dwServiceSpecificExitCode = 0;
m_status.dwCurrentState = dwCurrentState;
m_status.dwWin32ExitCode = dwWin32ExitCode;
m_status.dwWaitHint = dwWaitHint;
m_status.dwCheckPoint =
((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED)) ?
0 : dwCheckPoint++;
// Report the status of the service to the SCM.
::SetServiceStatus(m_statusHandle, &m_status);
}
static void OnServiceStart(DWORD dwArgc, PWSTR *pszArgv) {
WriteServiceLog("Service Starting", EVENTLOG_INFORMATION_TYPE);
SetServiceStatus(SERVICE_START_PENDING);
DWORD rv = g_service->OnStart(dwArgc, pszArgv);
if (rv) {
SetServiceStatus(SERVICE_STOPPED, rv);
} else {
SetServiceStatus(SERVICE_RUNNING);
}
}
static void OnServiceStop() {
WriteServiceLog("Service Stopping", EVENTLOG_INFORMATION_TYPE);
SetServiceStatus(SERVICE_STOP_PENDING);
g_service->OnStop();
SetServiceStatus(SERVICE_STOPPED);
}
static void OnServiceShutdown() {
g_service->OnShutdown();
SetServiceStatus(SERVICE_STOPPED);
}
static void WINAPI ServiceCtrlHandler(DWORD dwCtrl) {
switch (dwCtrl) {
case SERVICE_CONTROL_STOP: OnServiceStop(); break;
// case SERVICE_CONTROL_PAUSE: OnServicePause(); break;
// case SERVICE_CONTROL_CONTINUE: OnServiceContinue(); break;
case SERVICE_CONTROL_SHUTDOWN: OnServiceShutdown(); break;
case SERVICE_CONTROL_INTERROGATE: break;
default: break;
}
}
static void WINAPI ServiceMain(DWORD dwArgc, PWSTR *pszArgv) {
// Register the handler function for the service
m_statusHandle = RegisterServiceCtrlHandlerW(SERVICE_NAME, ServiceCtrlHandler);
if (m_statusHandle == NULL)
throw GetLastError();
// Start the service.
OnServiceStart(dwArgc, pszArgv);
}
static const SERVICE_TABLE_ENTRYW serviceTable[] = {
{SERVICE_NAME, ServiceMain},
{NULL, NULL}
};
PipeMessageHandler::PipeMessageHandler(const char *pipe_name, bool is_server_pipe, Delegate *delegate) {
pipe_name_ = _strdup(pipe_name);
is_server_pipe_ = is_server_pipe;
delegate_ = delegate;
pipe_ = INVALID_HANDLE_VALUE;
wait_handles_[0] = CreateEvent(NULL, TRUE, FALSE, NULL); // for ReadFile
wait_handles_[1] = CreateEvent(NULL, FALSE, FALSE, NULL); // For Exit
wait_handles_[2] = CreateEvent(NULL, TRUE, FALSE, NULL); // for WriteFile
packets_ = NULL;
thread_ = NULL;
packets_end_ = &packets_;
write_overlapped_active_ = false;
exit_ = false;
connection_established_ = false;
thread_id_ = 0;
}
PipeMessageHandler::~PipeMessageHandler() {
StopThread();
CloseHandle(wait_handles_[0]);
CloseHandle(wait_handles_[1]);
CloseHandle(wait_handles_[2]);
free(pipe_name_);
}
bool PipeMessageHandler::InitializeServerPipe() {
int BUFSIZE = 2048;
SECURITY_ATTRIBUTES saPipeSecurity = {0};
uint8 buf[SECURITY_DESCRIPTOR_MIN_LENGTH];
PSECURITY_DESCRIPTOR pPipeSD = (PSECURITY_DESCRIPTOR)buf;
if (!InitializeSecurityDescriptor(pPipeSD, SECURITY_DESCRIPTOR_REVISION))
return false;
// set NULL DACL on the SD
if (!SetSecurityDescriptorDacl(pPipeSD, TRUE, (PACL)NULL, FALSE))
return false;
// now set up the security attributes
saPipeSecurity.nLength = sizeof(SECURITY_ATTRIBUTES);
saPipeSecurity.bInheritHandle = TRUE;
saPipeSecurity.lpSecurityDescriptor = pPipeSD;
pipe_ = CreateNamedPipeW(L"\\\\.\\pipe\\TunSafe\\ServiceControl",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE, BUFSIZE, 0, &saPipeSecurity);
return pipe_ != INVALID_HANDLE_VALUE;
}
bool PipeMessageHandler::InitializeClientPipe() {
assert(pipe_ == INVALID_HANDLE_VALUE);
pipe_ = CreateFile(
pipe_name_,
GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (pipe_ == INVALID_HANDLE_VALUE)
return false;
DWORD mode = PIPE_READMODE_MESSAGE;
SetNamedPipeHandleState(pipe_, &mode, NULL, NULL);
return true;
}
void PipeMessageHandler::ClosePipe() {
if (pipe_ != INVALID_HANDLE_VALUE) {
CancelIo(pipe_);
CloseHandle(pipe_);
pipe_ = INVALID_HANDLE_VALUE;
}
connection_established_ = false;
write_overlapped_active_ = false;
packets_mutex_.Acquire();
OutgoingPacket *packets = packets_;
packets_ = NULL;
packets_end_ = &packets_;
packets_mutex_.Release();
while (packets) {
OutgoingPacket *p = packets;
packets = p->next;
free(p);
}
}
bool PipeMessageHandler::WritePacket(int type, const uint8 *data, size_t data_size) {
OutgoingPacket *packet = (OutgoingPacket *)malloc(offsetof(OutgoingPacket, data[data_size + 1]));
if (packet) {
packet->size = (uint32)(data_size + 1);
packet->data[0] = type;
memcpy(packet->data + 1, data, data_size);
packet->next = NULL;
packets_mutex_.Acquire();
OutgoingPacket *was_empty = packets_;
// login messages are always queued up front
if (type == SERVICE_REQ_LOGIN) {
packet->next = packets_;
if (packet->next == NULL)
packets_end_ = &packet->next;
packets_ = packet;
} else {
*packets_end_ = packet;
packets_end_ = &packet->next;
}
packets_mutex_.Release();
if (was_empty == NULL) {
// Only allow the pipe thread to invoke the send
if (GetCurrentThreadId() == thread_id_) {
SendNextQueuedWrite();
} else {
SetEvent(wait_handles_[1]);
}
}
}
return true;
}
void PipeMessageHandler::SendNextQueuedWrite() {
assert(thread_id_ == GetCurrentThreadId());
if (!write_overlapped_active_) {
OutgoingPacket *p = packets_;
if (p && connection_established_) {
memset(&write_overlapped_, 0, sizeof(write_overlapped_));
write_overlapped_.hEvent = wait_handles_[2];
if (WriteFile(pipe_, p->data, p->size, NULL, &write_overlapped_) || GetLastError() == ERROR_IO_PENDING)
write_overlapped_active_ = true;
}
}
}
uint8 *PipeMessageHandler::ReadNamedPipeAsync(size_t *packet_size) {
OVERLAPPED ov = {0};
uint8 *result = NULL;
DWORD bytes_waiting = 0;
DWORD rv;
ov.hEvent = wait_handles_[0];
if (!ReadFile(pipe_, NULL, 0, NULL, &ov)) {
rv = GetLastError();
if (rv != ERROR_IO_PENDING && rv != ERROR_MORE_DATA)
goto getout;
}
if (!WaitAndHandleWrites(INFINITE)) {
CancelIo(pipe_);
write_overlapped_active_ = false;
goto getout;
}
PeekNamedPipe(pipe_, NULL, 0, NULL, &bytes_waiting, NULL);
if (bytes_waiting == 0)
goto getout; // this is typically what happens when pipe closes.
result = (uint8*)malloc(bytes_waiting);
if (!result)
goto getout;
if (!ReadFile(pipe_, result, bytes_waiting, NULL, &ov)) {
rv = GetLastError();
if (rv != ERROR_IO_PENDING)
goto getout;
}
if (!WaitAndHandleWrites(1000)) {
CancelIo(pipe_);
write_overlapped_active_ = false;
free(result);
result = NULL;
goto getout;
}
bytes_waiting = (uint32)ov.InternalHigh;
if (bytes_waiting == 0) {
free(result);
result = NULL;
goto getout;
}
*packet_size = bytes_waiting;
getout:
return result;
}
bool PipeMessageHandler::ConnectNamedPipeAsync() {
OVERLAPPED ov = {0};
DWORD rv;
bool result = false;
ov.hEvent = wait_handles_[0];
if (!ConnectNamedPipe(pipe_, &ov)) {
rv = GetLastError();
if (rv != ERROR_PIPE_CONNECTED && rv != ERROR_IO_PENDING)
goto getout;
}
if (!WaitAndHandleWrites(INFINITE)) {
CancelIo(pipe_);
write_overlapped_active_ = false;
goto getout;
}
result = true;
getout:
return result;
}
bool PipeMessageHandler::WaitAndHandleWrites(int delay) {
DWORD rv;
assert(thread_id_ == GetCurrentThreadId());
again:
rv = WaitForMultipleObjects(2 + write_overlapped_active_, wait_handles_, FALSE, delay);
if (rv == WAIT_OBJECT_0 + 2) {
assert(write_overlapped_active_);
write_overlapped_active_ = false;
// Remove the packet from the front of the queue, now
// that it was sent.
packets_mutex_.Acquire();
OutgoingPacket *p = packets_;
if ((packets_ = p->next) == NULL)
packets_end_ = &packets_;
packets_mutex_.Release();
free(p);
SendNextQueuedWrite();
goto again;
}
if (rv == WAIT_OBJECT_0 + 1) {
if (exit_ || !delegate_->HandleNotify())
return false;
SendNextQueuedWrite();
goto again;
}
return rv == WAIT_OBJECT_0;
}
DWORD WINAPI PipeMessageHandler::StaticThreadMain(void *x) {
return ((PipeMessageHandler*)x)->ThreadMain();
}
bool PipeMessageHandler::VerifyThread() {
return thread_id_ == GetCurrentThreadId();
}
DWORD PipeMessageHandler::ThreadMain() {
assert((thread_id_ = GetCurrentThreadId()) != 0);
while (!exit_) {
// Create a named pipe and wait for connections from the UI process
if (is_server_pipe_) {
if (!InitializeServerPipe()) {
if (!exit_)
ExitProcess(1);
break;
}
// Wait for a client to connect to us.
if (!ConnectNamedPipeAsync()) {
if (!exit_)
ExitProcess(1);
break;
}
} else {
if (!InitializeClientPipe()) {
RINFO("Unable to connect to the TunSafe Service. Please make sure it's running.");
break;
}
}
connection_established_ = true;
if (!delegate_->HandleNewConnection())
goto closepipe;
SendNextQueuedWrite();
// Read/Process each message
for (;;) {
size_t message_size;
uint8 *message = ReadNamedPipeAsync(&message_size);
if (!message)
break;
if (message_size) {
if (!delegate_->HandleMessage(message[0], message + 1, message_size - 1)) {
FlushWrites(1000);
break;
}
}
free(message);
}
if (exit_)
break;
delegate_->HandleDisconnect();
if (!is_server_pipe_)
break;
closepipe:
ClosePipe();
}
ClosePipe();
return 0;
}
void PipeMessageHandler::FlushWrites(int delay) {
ResetEvent(wait_handles_[0]);
WaitAndHandleWrites(1000);
}
bool PipeMessageHandler::StartThread() {
DWORD thread_id;
assert(thread_ == NULL);
thread_ = CreateThread(NULL, 0, &StaticThreadMain, this, 0, &thread_id);
return thread_ != NULL;
}
void PipeMessageHandler::StopThread() {
if (thread_ != NULL) {
exit_ = true;
SetEvent(wait_handles_[1]);
WaitForSingleObject(thread_, INFINITE);
CloseHandle(thread_);
thread_ = NULL;
}
ClosePipe();
}
TunsafeServiceImpl::TunsafeServiceImpl()
: message_handler_(PIPE_NAME, true, this) {
thread_delegate_ = CreateTunsafeBackendDelegateThreaded(this, [=] {
SetEvent(message_handler_.notify_handle());
});
backend_ = CreateNativeTunsafeBackend(thread_delegate_);
historical_log_lines_count_ = historical_log_lines_pos_ = 0;
last_line_sent_ = 0;
did_send_getstate_ = false;
memset(historical_log_lines_, 0, sizeof(historical_log_lines_));
hkey_ = NULL;
want_graph_type_ = 0xffffffff;
RegCreateKeyEx(HKEY_LOCAL_MACHINE, "Software\\TunSafe", NULL, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey_, NULL);
}
TunsafeServiceImpl::~TunsafeServiceImpl() {
RegCloseKey(hkey_);
}
static wchar_t *RegReadStrW(HKEY hkey, const wchar_t *key, const wchar_t *def) {
wchar_t buf[1024];
DWORD n = sizeof(buf) - 2;
DWORD type = 0;
if (RegQueryValueExW(hkey, key, NULL, &type, (BYTE*)buf, &n) != ERROR_SUCCESS || type != REG_SZ)
return def ? _wcsdup(def) : NULL;
n >>= 1;
if (n && buf[n - 1] == 0)
n--;
buf[n] = 0;
return _wcsdup(buf);
}
unsigned TunsafeServiceImpl::OnStart(int argc, wchar_t **argv) {
message_handler_.StartThread();
uint32 service_flags = RegReadInt(hkey_, "ServiceStartupFlags", 0);
if ( (service_flags & kStartupFlag_BackgroundService) && (service_flags & kStartupFlag_ConnectWhenWindowsStarts) ) {
char *conf = RegReadStr(hkey_, "LastUsedConfigFile", "");
if (conf && *conf) {
current_filename_ = (char*)conf;
backend_->Start((char*)conf);
}
free(conf);
}
return 0;
}
bool TunsafeServiceImpl::AuthenticateUser() {
did_authenticate_user_ = true;
if (!ImpersonateNamedPipeClient(message_handler_.pipe_handle()))
return false;
wchar_t *user = GetUsernameOfCurrentUser(true);
RevertToSelf();
if (!user)
return false;
wchar_t *valid_user = RegReadStrW(hkey_, L"AllowedUsername", L"");
bool rv = valid_user && wcscmp(user, valid_user) == 0;
free(user);
free(valid_user);
return rv;
}
bool TunsafeServiceImpl::HandleMessage(int type, uint8 *data, size_t size) {
if (!did_authenticate_user_) {
if (type != SERVICE_REQ_LOGIN || size < 8 || *(uint64*)data != kTunsafeServiceProtocolVersion) {
const char *s = "Versioning Problem: The TunSafe service is a different version than the UI.";
message_handler_.WritePacket(SERVICE_MSG_LOGLINE, (uint8*)s, strlen(s));
return false;
}
if (!AuthenticateUser()) {
const char *s = "Permission Problem: Your Windows account is different from the account\r\nthat installed the TunSafe Service. Please reinstall it.\r\n";
message_handler_.WritePacket(SERVICE_MSG_LOGLINE, (uint8*)s, strlen(s));
return false;
}
}
switch (type) {
case SERVICE_REQ_START:
if (data[size - 1] != 0)
return false;
// Don't allow reading arbitrary files on disk
if (!EnsureValidConfigPath((char*)data)) {
char buf[MAX_PATH];
GetConfigPath(buf, sizeof(buf));
char *s = str_cat_alloc("Permission Problem: The Config file is in an unsafe location.\r\n Must be in:", buf, "\r\n");
message_handler_.WritePacket(SERVICE_MSG_LOGLINE, (uint8*)s, strlen(s));
free(s);
return false;
}
g_allow_pre_post = RegReadInt(hkey_, "AllowPrePost", 0) != 0;
current_filename_ = (char*)data;
backend_->Start((char*)data);
RegWriteStr(hkey_, "LastUsedConfigFile", (char*)data);
break;
case SERVICE_REQ_STOP:
backend_->Stop();
RegWriteStr(hkey_, "LastUsedConfigFile", "");
OnStateChanged();
break;
case SERVICE_REQ_LOGIN:
did_send_getstate_ = true;
OnStatusCode(backend_->status());
OnStateChanged();
SendQueuedLogLines();
break;
case SERVICE_REQ_GETSTATS:
if (size < 1) return false;
backend_->RequestStats(data[0] != 0);
break;
case SERVICE_REQ_SET_INTERNET_BLOCKSTATE:
if (size < 1)
return false;
backend_->SetInternetBlockState((InternetBlockState)data[0]);
OnStateChanged();
break;
case SERVICE_REQ_RESETSTATS:
backend_->ResetStats();
break;
case SERVICE_REQ_GET_GRAPH:
if (size < 4) return false;
want_graph_type_ = *(int*)data;
TunsafeServiceImpl::OnGraphAvailable();
break;
case SERVICE_REQ_SET_STARTUP_FLAGS:
if (size < 4)
return false;
RegSetValueEx(hkey_, "ServiceStartupFlags", NULL, REG_DWORD, (BYTE*)data, 4);
break;
default:
return false;
}
return true;
}
bool TunsafeServiceImpl::HandleNotify() {
thread_delegate_->DoWork();
return true;
}
bool TunsafeServiceImpl::HandleNewConnection() {
did_send_getstate_ = false;
did_authenticate_user_ = false;
last_line_sent_ = 0;
return true;
}
void TunsafeServiceImpl::HandleDisconnect() {
want_graph_type_ = 0xffffffff;
backend_->RequestStats(false);
uint32 service_flags = RegReadInt(hkey_, "ServiceStartupFlags", 0);
if (!(service_flags & kStartupFlag_BackgroundService))
backend_->Stop();
}
void TunsafeServiceImpl::OnGraphAvailable() {
if (want_graph_type_ != 0xffffffff) {
LinearizedGraph *graph = backend_->GetGraph(want_graph_type_);
if (graph)
message_handler_.WritePacket(SERVICE_MSG_GRAPH, (uint8*)graph, graph->total_size);
}
}
void TunsafeServiceImpl::SendQueuedLogLines() {
assert(message_handler_.VerifyThread());
uint32 maxi = std::min<uint32>(historical_log_lines_count_, historical_log_lines_pos_ - last_line_sent_);
last_line_sent_ = historical_log_lines_pos_;
for (uint32 i = 0; i < maxi; i++) {
const char *s = historical_log_lines_[(historical_log_lines_pos_ - maxi + i) & (LOGLINE_COUNT - 1)];
if (s)
message_handler_.WritePacket(SERVICE_MSG_LOGLINE, (uint8*)s, strlen(s));
}
}
void TunsafeServiceImpl::OnClearLog() {
historical_log_lines_pos_ = 0;
historical_log_lines_count_ = 0;
message_handler_.WritePacket(SERVICE_MSG_CLEARLOG, NULL, 0);
}
void TunsafeServiceImpl::OnLogLine(const char **s) {
assert(message_handler_.VerifyThread());
char *ss = (char*)*s;
*s = NULL;
char *&x = historical_log_lines_[historical_log_lines_pos_++ & (LOGLINE_COUNT - 1)];
std::swap(x, ss);
if (historical_log_lines_count_ < LOGLINE_COUNT)
historical_log_lines_count_++;
free(ss);
if (did_send_getstate_)
SendQueuedLogLines();
}
void TunsafeServiceImpl::OnGetStats(const WgProcessorStats &stats) {
message_handler_.WritePacket(SERVICE_MSG_STATS, (uint8*)&stats, sizeof(stats));
}
void TunsafeServiceImpl::OnStateChanged() {
uint8 *temp = new uint8[current_filename_.size() + 1 + sizeof(ServiceState)];
bool is_activated;
memset(temp, 0, sizeof(ServiceState));
ServiceState *ss = (ServiceState *)temp;
ss->is_started = backend_->is_started();
ss->internet_block_state = backend_->GetInternetBlockState(&is_activated);
ss->internet_block_state_active = is_activated;
ss->ipv4_ip = backend_->GetIP();
memcpy(ss->public_key, backend_->public_key(), 32);
memcpy(temp + sizeof(ServiceState), current_filename_.c_str(), current_filename_.size() + 1);
message_handler_.WritePacket(SERVICE_MSG_STATE, temp, current_filename_.size() + 1 + sizeof(ServiceState));
delete[] temp;
}
void TunsafeServiceImpl::OnStatusCode(TunsafeBackend::StatusCode status) {
if (status == TunsafeBackend::kStatusConnected)
OnStateChanged(); // ensure we know the ip first
uint32 v32 = (uint32)status;
message_handler_.WritePacket(SERVICE_MSG_STATUS_CODE, (uint8*)&v32, 4);
}
void TunsafeServiceImpl::OnStop() {
message_handler_.StopThread();
backend_->Stop();
}
void TunsafeServiceImpl::OnShutdown() {
}
static void PushServiceLine(const char *s) {
if (g_service) {
char buf[64];
SYSTEMTIME t;
size_t l = strlen(s);
GetLocalTime(&t);
snprintf(buf, sizeof(buf), "[%.2d:%.2d:%.2d] ", t.wHour, t.wMinute, t.wSecond);
size_t tl = strlen(buf);
char *x = (char*) malloc(tl + l + 3);
memcpy(x, buf, tl);
memcpy(x + tl, s, l);
x[l + tl] = '\r';
x[l + tl + 1] = '\n';
x[l + tl + 2] = '\0';
g_service->delegate()->OnLogLine((const char**)&x);
free(x);
} else {
size_t l = strlen(s);
char buf[1024];
SYSTEMTIME t;
GetLocalTime(&t);
snprintf(buf, sizeof(buf), "[%.2d:%.2d:%.2d] ", t.wHour, t.wMinute, t.wSecond);
size_t tl = strlen(buf);
if (l >= ARRAYSIZE(buf) - tl - 1)
l = ARRAYSIZE(buf) - tl - 1;
memcpy(buf + tl, s, l);
buf[l + tl] = '\0';
WriteServiceLog(buf, EVENTLOG_INFORMATION_TYPE);
}
}
BOOL RunProcessAsTunsafeServiceProcess() {
g_service = new TunsafeServiceImpl;
g_logger = &PushServiceLine;
//g_service->OnStart(NULL, 0);
//MessageBoxA(0, "Service running", "Service running", 0);
//return TRUE;
// while (true)Sleep(1000);
// Connects the main thread of a service process to the service control
// manager, which causes the thread to be the service control dispatcher
// thread for the calling process. This call returns when the service has
// stopped. The process should simply terminate when the call returns.
return StartServiceCtrlDispatcherW(serviceTable);
}
TunsafeServiceClient::TunsafeServiceClient(TunsafeBackend::Delegate *delegate)
: message_handler_(PIPE_NAME, false, this) {
is_remote_ = true;
got_state_from_control_ = false;
delegate_ = delegate;
cached_graph_ = 0;
last_graph_type_ = 0xffffffff;
memset(&service_state_, 0, sizeof(service_state_));
}
TunsafeServiceClient::~TunsafeServiceClient() {
message_handler_.StopThread();
}
bool TunsafeServiceClient::Initialize() {
// Wait for the service to start
last_graph_type_ = 0xffffffff;
return message_handler_.StartThread();
}
void TunsafeServiceClient::Start(const char *config_file) {
message_handler_.WritePacket(SERVICE_REQ_START, (uint8*)config_file, strlen(config_file) + 1);
}
void TunsafeServiceClient::Stop() {
message_handler_.WritePacket(SERVICE_REQ_STOP, NULL, 0);
}
void TunsafeServiceClient::RequestStats(bool enable) {
want_stats_ = enable;
if (message_handler_.is_connected())
message_handler_.WritePacket(SERVICE_REQ_GETSTATS, &want_stats_, 1);
}
void TunsafeServiceClient::ResetStats() {
message_handler_.WritePacket(SERVICE_REQ_RESETSTATS, NULL, 0);
}
InternetBlockState TunsafeServiceClient::GetInternetBlockState(bool *is_activated) {
if (is_activated)
*is_activated = service_state_.internet_block_state_active;
return (InternetBlockState)service_state_.internet_block_state;
}
void TunsafeServiceClient::SetInternetBlockState(InternetBlockState s) {
uint8 v = (uint8)s;
message_handler_.WritePacket(SERVICE_REQ_SET_INTERNET_BLOCKSTATE, &v, 1);
}
void TunsafeServiceClient::SetServiceStartupFlags(uint32 flags) {
message_handler_.WritePacket(SERVICE_REQ_SET_STARTUP_FLAGS, (uint8*)&flags, 4);
}
LinearizedGraph *TunsafeServiceClient::GetGraph(int type) {
if (type != last_graph_type_) {
last_graph_type_ = type;
message_handler_.WritePacket(SERVICE_REQ_GET_GRAPH, (uint8*)&type, 4);
}
mutex_.Acquire();
LinearizedGraph *graph = cached_graph_;
LinearizedGraph *new_graph = (graph && graph->graph_type == type) ? (LinearizedGraph*)memdup(graph, graph->total_size) : NULL;
mutex_.Release();
return new_graph;
}
std::string TunsafeServiceClient::GetConfigFileName() {
mutex_.Acquire();
std::string rv = config_file_;
mutex_.Release();
return rv;
}
bool TunsafeServiceClient::HandleMessage(int type, uint8 *data, size_t data_size) {
switch(type) {
case SERVICE_MSG_STATE:
if (data_size <= sizeof(service_state_) || data[data_size - 1])
return false;
got_state_from_control_ = true;
mutex_.Acquire();
config_file_.assign((char*)data + sizeof(service_state_), data_size - 1 - sizeof(service_state_));
memcpy(&service_state_, data, sizeof(service_state_));
memcpy(public_key_, service_state_.public_key, 32);
is_started_ = service_state_.is_started;
ipv4_ip_ = service_state_.ipv4_ip;
mutex_.Release();
delegate_->OnStateChanged();
return true;
case SERVICE_MSG_LOGLINE: {
if (data_size == 0)
return false;
char *s = my_strndup((char*)data, data_size);
delegate_->OnLogLine((const char **)&s);
free(s);
return true;
}
case SERVICE_MSG_STATS: {
WgProcessorStats stats;
if (data_size != sizeof(WgProcessorStats))
return false;
memcpy(&stats, data, sizeof(WgProcessorStats));
delegate_->OnGetStats(stats);
return true;
}
case SERVICE_MSG_CLEARLOG:
delegate_->OnClearLog();
return true;
case SERVICE_MSG_STATUS_CODE:
if (data_size < 4)
return false;
status_ = (StatusCode)*(uint32*)data;
delegate_->OnStatusCode(status_);
return true;
case SERVICE_MSG_GRAPH:
if (data_size < 4 || data_size != *(uint32*)data)
return false;
LinearizedGraph *graph = (LinearizedGraph*)memdup(data, data_size);
mutex_.Acquire();
std::swap(graph, cached_graph_);
mutex_.Release();
free(graph);
delegate_->OnGraphAvailable();
return true;
}
return false;
}
bool TunsafeServiceClient::HandleNotify() {
return true;
}
bool TunsafeServiceClient::HandleNewConnection() {
message_handler_.WritePacket(SERVICE_REQ_LOGIN, (uint8*)&kTunsafeServiceProtocolVersion, 8);
if (want_stats_)
message_handler_.WritePacket(SERVICE_REQ_GETSTATS, &want_stats_, 1);
return true;
}
void TunsafeServiceClient::HandleDisconnect() {
status_ = TunsafeBackend::kErrorServiceLost;
delegate_->OnStatusCode(TunsafeBackend::kErrorServiceLost);
}
void TunsafeServiceClient::Teardown() {
message_handler_.StopThread();
}
TunsafeBackend *CreateTunsafeServiceClient(TunsafeBackend::Delegate *delegate) {
TunsafeServiceClient *client = new TunsafeServiceClient(delegate);
if (client && !client->Initialize()) {
delete client;
client = NULL;
}
return client;
}