cf92ac7a0c
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
378 lines
9.7 KiB
C++
378 lines
9.7 KiB
C++
// SPDX-License-Identifier: AGPL-1.0-only
|
|
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
|
|
#include "stdafx.h"
|
|
#include "util_win32.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <shldisp.h>
|
|
#include <shlobj.h>
|
|
#include <exdisp.h>
|
|
#include <atlbase.h>
|
|
|
|
const char *FindFilenameComponent(const char *s) {
|
|
size_t len = strlen(s);
|
|
for (;;) {
|
|
if (len == 0)
|
|
return "";
|
|
len--;
|
|
if (s[len] == '\\' || s[len] == '/')
|
|
break;
|
|
}
|
|
return s + len + 1;
|
|
}
|
|
|
|
void str_set(char **x, const char *s) {
|
|
free(*x);
|
|
*x = _strdup(s);
|
|
}
|
|
|
|
char *str_cat_alloc(const char * const *a, size_t n) {
|
|
if (n > 32) return NULL;
|
|
size_t len[32], totlen = 0;
|
|
for (size_t i = 0; i < n; i++) {
|
|
len[i] = strlen(a[i]);
|
|
totlen += len[i];
|
|
}
|
|
char *r = (char *)malloc(totlen + 1);
|
|
totlen = 0;
|
|
for (size_t i = 0; i < n; i++) {
|
|
size_t n = len[i];
|
|
memcpy(r + totlen, a[i], n);
|
|
totlen += n;
|
|
}
|
|
r[totlen] = 0;
|
|
return r;
|
|
}
|
|
|
|
char *str_cat_alloc(const char *a, const char *b) {
|
|
const char * x[2] = {a, b};
|
|
return str_cat_alloc(x, 2);
|
|
}
|
|
|
|
char *str_cat_alloc(const char *a, const char *b, const char *c) {
|
|
const char * x[3] = {a, b, c};
|
|
return str_cat_alloc(x, 3);
|
|
}
|
|
|
|
|
|
int RegReadInt(HKEY hkey, const char *key, int def) {
|
|
DWORD value = def, n = sizeof(value);
|
|
RegQueryValueEx(hkey, key, NULL, NULL, (BYTE*)&value, &n);
|
|
return value;
|
|
}
|
|
|
|
void RegWriteInt(HKEY hkey, const char *key, int value) {
|
|
RegSetValueEx(hkey, key, NULL, REG_DWORD, (BYTE*)&value, sizeof(value));
|
|
}
|
|
|
|
char *RegReadStr(HKEY hkey, const char *key, const char *def) {
|
|
char buf[1024];
|
|
DWORD n = sizeof(buf) - 1;
|
|
DWORD type = 0;
|
|
if (RegQueryValueEx(hkey, key, NULL, &type, (BYTE*)buf, &n) != ERROR_SUCCESS || type != REG_SZ)
|
|
return def ? _strdup(def) : NULL;
|
|
if (n && buf[n - 1] == 0)
|
|
n--;
|
|
buf[n] = 0;
|
|
return _strdup(buf);
|
|
}
|
|
|
|
void RegWriteStr(HKEY hkey, const char *key, const char *v) {
|
|
RegSetValueEx(hkey, key, NULL, REG_SZ, (BYTE*)v, (DWORD)strlen(v) + 1);
|
|
}
|
|
|
|
bool GetProcessElevationType(TOKEN_ELEVATION_TYPE *pOutElevationType) {
|
|
*pOutElevationType = TokenElevationTypeDefault;
|
|
bool fResult = false;
|
|
HANDLE hProcToken = NULL;
|
|
if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hProcToken)) {
|
|
DWORD dwSize = 0;
|
|
TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeDefault;
|
|
if (::GetTokenInformation(hProcToken, TokenElevationType, &elevationType, sizeof(elevationType), &dwSize)
|
|
&& dwSize == sizeof(elevationType)) {
|
|
*pOutElevationType = elevationType;
|
|
fResult = true;
|
|
}
|
|
::CloseHandle(hProcToken);
|
|
}
|
|
return fResult;
|
|
}
|
|
|
|
/*++
|
|
Routine Description: This routine returns TRUE if the caller's
|
|
process is a member of the Administrators local group. Caller is NOT
|
|
expected to be impersonating anyone and is expected to be able to
|
|
open its own process and process token.
|
|
Arguments: None.
|
|
Return Value:
|
|
TRUE - Caller has Administrators local group.
|
|
FALSE - Caller does not have Administrators local group. --
|
|
*/
|
|
|
|
BOOL IsUserAdmin(VOID) {
|
|
BOOL b;
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
PSID AdministratorsGroup;
|
|
b = AllocateAndInitializeSid(
|
|
&NtAuthority,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0,
|
|
&AdministratorsGroup);
|
|
if (b) {
|
|
if (!CheckTokenMembership(NULL, AdministratorsGroup, &b)) {
|
|
b = FALSE;
|
|
}
|
|
FreeSid(AdministratorsGroup);
|
|
}
|
|
|
|
return(b);
|
|
}
|
|
|
|
|
|
const wchar_t *SkipAppNameInCommandLineArgs(const wchar_t *s) {
|
|
if (*s == '\"') {
|
|
for (;;) {
|
|
s++;
|
|
if (*s == 0) return s;
|
|
if (*s == '\"') return s + 1;
|
|
}
|
|
} else {
|
|
for (;;) {
|
|
if (*s == 0) return s;
|
|
if (*s == ' ') return s + 1;
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint8* LoadFileSane(const char *name, size_t *size) {
|
|
FILE *f = fopen(name, "rb");
|
|
uint8 *new_file = NULL, *file = NULL;
|
|
size_t j, i, n;
|
|
if (!f) return false;
|
|
fseek(f, 0, SEEK_END);
|
|
long x = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (x < 0 || x >= 65536) goto error;
|
|
file = (uint8*)malloc(x + 1);
|
|
if (!file) goto error;
|
|
n = fread(file, 1, x + 1, f);
|
|
if (n != x || !SanityCheckBuf(file, n))
|
|
goto error;
|
|
// Convert the file to DOS new lines
|
|
for (i = j = 0; i < n; i++)
|
|
j += (file[i] == '\n');
|
|
new_file = (uint8*)malloc(n + 1 + j);
|
|
if (!new_file) goto error;
|
|
for (i = j = 0; i < n; i++) {
|
|
uint8 c = file[i];
|
|
if (c == '\r')
|
|
continue;
|
|
if (c == '\n')
|
|
new_file[j++] = '\r';
|
|
new_file[j++] = c;
|
|
}
|
|
new_file[j] = 0;
|
|
*size = j;
|
|
|
|
error:
|
|
fclose(f);
|
|
free(file);
|
|
return new_file;
|
|
}
|
|
|
|
int WriteOutFile(const char *filename, uint8 *filedata, size_t filesize) {
|
|
FILE *f = fopen(filename, "wb");
|
|
if (!f) return kWriteOutFile_AccessError;
|
|
if (fwrite(filedata, 1, filesize, f) != filesize) {
|
|
fclose(f);
|
|
return kWriteOutFile_OtherError;
|
|
}
|
|
fclose(f);
|
|
return kWriteOutFile_Ok;
|
|
}
|
|
|
|
bool FileExists(const CHAR *fileName) {
|
|
DWORD fileAttr = GetFileAttributes(fileName);
|
|
return (0xFFFFFFFF != fileAttr);
|
|
}
|
|
|
|
__int64 FileSize(const char* name) {
|
|
WIN32_FILE_ATTRIBUTE_DATA fad;
|
|
if (!GetFileAttributesEx(name, GetFileExInfoStandard, &fad))
|
|
return -1; // error condition, could call GetLastError to find out more
|
|
LARGE_INTEGER size;
|
|
size.HighPart = fad.nFileSizeHigh;
|
|
size.LowPart = fad.nFileSizeLow;
|
|
return size.QuadPart;
|
|
}
|
|
|
|
static bool is_space(uint8_t c) {
|
|
return c == ' ' || c == '\r' || c == '\n' || c == '\t';
|
|
}
|
|
|
|
static bool is_valid(uint8_t c) {
|
|
return c >= ' ' || c == '\r' || c == '\n' || c == '\t';
|
|
}
|
|
|
|
bool SanityCheckBuf(uint8 *buf, size_t n) {
|
|
for (size_t i = 0; i < n; i++) {
|
|
if (!is_space(buf[i])) {
|
|
if (buf[i] != '[' && buf[i] != '#')
|
|
return false;
|
|
for (; i < n; i++)
|
|
if (!is_valid(buf[i]))
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FindDesktopFolderView(REFIID riid, void **ppv) {
|
|
CComPtr<IShellWindows> spShellWindows;
|
|
spShellWindows.CoCreateInstance(CLSID_ShellWindows);
|
|
|
|
CComVariant vtLoc(CSIDL_DESKTOP);
|
|
CComVariant vtEmpty;
|
|
long lhwnd;
|
|
CComPtr<IDispatch> spdisp;
|
|
spShellWindows->FindWindowSW(
|
|
&vtLoc, &vtEmpty,
|
|
SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
|
|
|
|
CComPtr<IShellBrowser> spBrowser;
|
|
CComQIPtr<IServiceProvider>(spdisp)->
|
|
QueryService(SID_STopLevelBrowser,
|
|
IID_PPV_ARGS(&spBrowser));
|
|
|
|
CComPtr<IShellView> spView;
|
|
spBrowser->QueryActiveShellView(&spView);
|
|
|
|
spView->QueryInterface(riid, ppv);
|
|
}
|
|
|
|
void GetDesktopAutomationObject(REFIID riid, void **ppv) {
|
|
CComPtr<IShellView> spsv;
|
|
FindDesktopFolderView(IID_PPV_ARGS(&spsv));
|
|
CComPtr<IDispatch> spdispView;
|
|
spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
|
|
spdispView->QueryInterface(riid, ppv);
|
|
}
|
|
|
|
void ShellExecuteFromExplorer(
|
|
PCSTR pszFile,
|
|
PCSTR pszParameters,
|
|
PCSTR pszDirectory,
|
|
PCSTR pszOperation,
|
|
int nShowCmd) {
|
|
CComPtr<IShellFolderViewDual> spFolderView;
|
|
GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
|
|
CComPtr<IDispatch> spdispShell;
|
|
spFolderView->get_Application(&spdispShell);
|
|
|
|
CComQIPtr<IShellDispatch2>(spdispShell)
|
|
->ShellExecute(CComBSTR(pszFile),
|
|
CComVariant(pszParameters ? pszParameters : ""),
|
|
CComVariant(pszDirectory ? pszDirectory : ""),
|
|
CComVariant(pszOperation ? pszOperation : ""),
|
|
CComVariant(nShowCmd));
|
|
}
|
|
|
|
size_t GetConfigPath(char *path, size_t path_size) {
|
|
|
|
if (!GetModuleFileName(NULL, path, (DWORD)path_size)) {
|
|
*path = 0;
|
|
return 0;
|
|
}
|
|
char *last = (char *)FindFilenameComponent(path);
|
|
if (!*last || last + 8 > path + path_size) {
|
|
*path = 0;
|
|
return 0;
|
|
}
|
|
memcpy(last, "Config\\", 8 * sizeof(last[0]));
|
|
return last + 7 - path;
|
|
}
|
|
|
|
static bool ContainsDotDot(const char *path) {
|
|
for (uint8 last = 0, cur; (cur = path[0]) != '\0'; last = cur, path++)
|
|
if (cur == '.' && last == cur)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EnsureValidConfigPath(const char *path) {
|
|
char buf[1024];
|
|
|
|
size_t len = GetConfigPath(buf, sizeof(buf));
|
|
return (len != 0) && (strlen(path) > len && memcmp(path, buf, len) == 0 && !ContainsDotDot(path + len));
|
|
}
|
|
|
|
bool RunProcessAsAdminWithArgs(const char *args, bool wait_for_exit) {
|
|
SHELLEXECUTEINFO shExecInfo = {0};
|
|
char buf[1024];
|
|
|
|
if (!GetModuleFileName(NULL, buf, 1024))
|
|
return false;
|
|
shExecInfo.cbSize = sizeof(shExecInfo);
|
|
shExecInfo.lpVerb = "runas";
|
|
shExecInfo.lpFile = buf;
|
|
shExecInfo.lpParameters = args;
|
|
shExecInfo.nShow = SW_SHOW;
|
|
shExecInfo.fMask = SEE_MASK_NOASYNC | wait_for_exit * SEE_MASK_NOCLOSEPROCESS;
|
|
if (!ShellExecuteExA(&shExecInfo))
|
|
return false;
|
|
if (shExecInfo.hProcess) {
|
|
WaitForSingleObject(shExecInfo.hProcess, 10000);
|
|
CloseHandle(shExecInfo.hProcess);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RestartProcessAsAdministrator() {
|
|
SHELLEXECUTEINFOW shExecInfo = {0};
|
|
wchar_t buf[1024];
|
|
|
|
if (!GetModuleFileNameW(NULL, buf, 1024))
|
|
return false;
|
|
|
|
// shExecInfo.hwnd = window;
|
|
shExecInfo.cbSize = sizeof(shExecInfo);
|
|
shExecInfo.lpVerb = L"runas";
|
|
shExecInfo.lpFile = buf;
|
|
shExecInfo.lpParameters = SkipAppNameInCommandLineArgs(GetCommandLineW());
|
|
shExecInfo.nShow = SW_SHOW;
|
|
|
|
return ShellExecuteExW(&shExecInfo) != 0;
|
|
}
|
|
|
|
bool SetClipboardString(const char *string) {
|
|
bool ok = false;
|
|
if (OpenClipboard(NULL)) {
|
|
HGLOBAL hglb;
|
|
size_t len = strlen(string);
|
|
hglb = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, (len + 1) * sizeof(char));
|
|
LPSTR lptstr = (LPSTR)GlobalLock(hglb);
|
|
memcpy(lptstr, string, len + 1);
|
|
GlobalUnlock(hglb);
|
|
EmptyClipboard();
|
|
ok = SetClipboardData(CF_TEXT, hglb) != 0;
|
|
CloseClipboard();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
RECT GetParentRect(HWND wnd) {
|
|
RECT btrect;
|
|
GetClientRect(wnd, &btrect);
|
|
MapWindowPoints(wnd, GetParent(wnd), (LPPOINT)&btrect, 2);
|
|
return btrect;
|
|
}
|
|
|
|
RECT MakeRect(int l, int t, int r, int b) {
|
|
RECT rr = { l, t, r, b };
|
|
return rr;
|
|
}
|