tunsafe-clang15/tunsafe_win32.cpp

1827 lines
56 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: AGPL-1.0-only
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
#include "stdafx.h"
#include "wireguard_config.h"
#include "network_win32_api.h"
#include "network_win32_dnsblock.h"
#include <Commctrl.h>
#include <stdlib.h>
#include <assert.h>
#include <malloc.h>
#include <stddef.h>
#include "resource.h"
#include <string.h>
#include <Richedit.h>
#include <vector>
#include <Iphlpapi.h>
#include <assert.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include "tunsafe_endian.h"
#include "util.h"
#include <atlbase.h>
#include <algorithm>
#include "crypto/curve25519-donna.h"
#include "service_win32.h"
#include "util_win32.h"
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "rpcrt4.lib")
#pragma comment(lib,"comctl32.lib")
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
void InitCpuFeatures();
void PrintCpuFeatures();
void Benchmark();
static const char *GetCurrentConfigTitle(char *buf, size_t max_size);
static char *PrintMB(char *buf, int64 bytes);
static void LoadConfigFile(const char *filename, bool save, bool force_start);
static void SetCurrentConfigFilename(const char *filename);
static void CreateLocalOrRemoteBackend(bool remote);
static void UpdateGraphReq();
#pragma warning(disable: 4200)
static bool g_is_connected_to_server;
static bool g_notified_connected_server;
static HWND g_ui_window;
static HICON g_icons[2];
static bool g_minimize_on_connect;
static bool g_ui_visible;
static char *g_current_filename;
static HINSTANCE g_hinstance;
static TunsafeBackend *g_backend;
static TunsafeBackend::Delegate *g_backend_delegate;
static const char *g_cmdline_filename;
static bool g_first_state_msg;
static bool g_is_limited_uac_account;
static bool g_is_tunsafe_service_running;
static bool g_disable_connect_on_start;
static bool g_not_first_status_msg;
static HANDLE g_runonce_mutex;
static int g_startup_flags;
static HKEY g_reg_key;
static HKEY g_hklm_reg_key;
static HKEY g_hklm_readonly_reg_key;
static HWND hwndPaintBox, hwndStatus, hwndGraphBox, hwndTab, hwndAdvancedBox, hwndEdit;
static WgProcessorStats g_processor_stats;
static int g_large_fonts;
static TunsafeBackend::StatusCode g_status_code;
static UINT g_message_taskbar_created;
static int g_current_tab;
static bool wm_dropfiles_recursive;
static bool g_has_icon;
static int g_selected_graph_type;
static RECT comborect;
static HBITMAP arrowbitmap;
static uint32 g_timestamp_of_exit_menuloop;
enum UpdateIconWhy {
UIW_NONE = 0,
UIW_STOPPED_WORKING_FAIL = 1,
UIW_START = 2,
};
static void UpdateIcon(UpdateIconWhy error);
int RescaleDpi(int size) {
return (g_large_fonts == 96) ? size : size * g_large_fonts / 96;
}
RECT RescaleDpiRect(const RECT &r) {
RECT rr = r;
if (g_large_fonts != 96) {
rr.left = rr.left * g_large_fonts / 96;
rr.top = rr.top * g_large_fonts / 96;
rr.right = rr.right * g_large_fonts / 96;
rr.bottom = rr.bottom * g_large_fonts / 96;
}
return rr;
}
static void SetUiVisibility(bool visible) {
g_ui_visible = visible;
ShowWindow(g_ui_window, visible ? SW_SHOW : SW_HIDE);
g_backend->RequestStats(visible);
UpdateGraphReq();
}
static bool GetConfigFullName(const char *basename, char *fullname, size_t fullname_size) {
size_t len = strlen(basename);
if (FindFilenameComponent(basename)[0]) {
if (len >= fullname_size)
return false;
memcpy(fullname, basename, len + 1);
return true;
}
size_t clen = GetConfigPath(fullname, fullname_size);
if (clen == 0 || clen + len >= fullname_size)
return false;
memcpy(fullname + clen, basename, (len + 1) * sizeof(fullname[0]));
return true;
}
void StopTunsafeBackend(UpdateIconWhy why) {
if (g_backend->is_started()) {
g_backend->Stop();
if (g_is_connected_to_server)
RINFO("Disconnected");
g_is_connected_to_server = false;
UpdateIcon(why);
RegWriteInt(g_reg_key, "IsConnected", 0);
}
}
const char *print_ip(char buf[kSizeOfAddress], in_addr_t ip) {
snprintf(buf, kSizeOfAddress, "%d.%d.%d.%d", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff);
return buf;
}
void StartTunsafeBackend(UpdateIconWhy reason) {
if (!*g_current_filename)
return;
// recreate service connection
if (g_backend->status() == TunsafeBackend::kErrorServiceLost)
CreateLocalOrRemoteBackend(g_backend->is_remote());
if (g_backend->is_remote() && !EnsureValidConfigPath(g_current_filename)) {
RERROR("The config file needs to be in the Config-directory. Maybe the TunSafe\r\n process doesn't match with the running service. Try selecting 'Don't Use a Service'.");
StopTunsafeBackend(UIW_NONE);
return;
}
g_notified_connected_server = false;
g_is_connected_to_server = false;
g_backend->Start(g_current_filename);
RegWriteInt(g_reg_key, "IsConnected", 1);
}
static void InvalidatePaintbox() {
InvalidateRect(hwndPaintBox, NULL, FALSE);
}
class MyBackendDelegate : public TunsafeBackend::Delegate {
public:
virtual void OnGraphAvailable() {
InvalidateRect(hwndGraphBox, NULL, FALSE);
}
virtual void OnGetStats(const WgProcessorStats &stats) {
g_processor_stats = stats;
InvalidatePaintbox();
char buf[64];
uint32 mbs_in = (uint32)(stats.tun_bytes_out_per_second * (1.0 / 1250));
uint32 gb_in = (uint32)(stats.tun_bytes_out * (1.0 / (1024 * 1024 * 1024 / 100)));
snprintf(buf, ARRAYSIZE(buf), "D: %d.%.2d Mbps (%d.%.2d GB)", mbs_in / 100, mbs_in % 100, gb_in / 100, gb_in % 100);
SendMessage(hwndStatus, SB_SETTEXT, 1, (LPARAM)buf);
uint32 mbs_out = (uint32)(stats.tun_bytes_in_per_second * (1.0 / 1250));
uint32 gb_out = (uint32)(stats.tun_bytes_in * (1.0 / (1024 * 1024 * 1024 / 100)));
snprintf(buf, ARRAYSIZE(buf), "U: %d.%.2d Mbps (%d.%.2d GB)", mbs_out / 100, mbs_out % 100, gb_out / 100, gb_out % 100);
SendMessage(hwndStatus, SB_SETTEXT, 2, (LPARAM)buf);
InvalidateRect(hwndAdvancedBox, NULL, FALSE);
}
virtual void OnLogLine(const char **s) {
CHARRANGE cr;
cr.cpMin = -1;
cr.cpMax = -1;
// hwnd = rich edit hwnd
SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&cr);
SendMessage(hwndEdit, EM_REPLACESEL, 0, (LPARAM)*s);
}
virtual void OnStateChanged() {
if (!g_first_state_msg) {
g_first_state_msg = true;
char fullname[1024];
const char *filename = g_cmdline_filename;
if (filename) {
if (GetConfigFullName(filename, fullname, sizeof(fullname)))
SetCurrentConfigFilename(fullname);
} else {
std::string currconfig = g_backend->GetConfigFileName();
if (currconfig.empty()) {
char *conf = RegReadStr(g_reg_key, "ConfigFile", "TunSafe.conf");
if (GetConfigFullName(conf, fullname, sizeof(fullname)))
SetCurrentConfigFilename(fullname);
free(conf);
} else {
SetCurrentConfigFilename(currconfig.c_str());
}
}
if (filename != NULL || !(g_startup_flags & kStartupFlag_BackgroundService) && !g_disable_connect_on_start && RegReadInt(g_reg_key, "IsConnected", 0)) {
StartTunsafeBackend(UIW_START);
} else {
if (!g_backend->is_started())
RINFO("Press Connect to initiate a connection to the WireGuard server.");
}
}
bool running = g_backend->is_started();
SetDlgItemText(g_ui_window, ID_START, running ? "Re&connect" : "&Connect");
InvalidatePaintbox();
EnableWindow(GetDlgItem(g_ui_window, ID_STOP), running);
}
virtual void OnStatusCode(TunsafeBackend::StatusCode status) override {
g_status_code = status;
if (TunsafeBackend::IsPermanentError(status)) {
UpdateIcon(g_is_connected_to_server ? UIW_STOPPED_WORKING_FAIL : UIW_NONE);
InvalidatePaintbox();
return;
}
bool is_connected = (status == TunsafeBackend::kStatusConnected);
if (is_connected && g_minimize_on_connect) {
g_minimize_on_connect = false;
SetUiVisibility(false);
}
bool not_first = g_not_first_status_msg;
g_not_first_status_msg = true;
if (is_connected != g_is_connected_to_server) {
g_is_connected_to_server = is_connected;
// avoid showing a notice if service is already connected
if (is_connected > not_first && (g_startup_flags & kStartupFlag_BackgroundService))
g_notified_connected_server = true;
UpdateIcon(UIW_NONE);
InvalidatePaintbox();
}
}
virtual void OnClearLog() override {
SetWindowText(hwndEdit, "");
}
};
static MyBackendDelegate my_procdel;
static void CreateLocalOrRemoteBackend(bool remote) {
delete g_backend;
g_first_state_msg = false;
if (!remote) {
g_backend = CreateNativeTunsafeBackend(g_backend_delegate);
} else {
RINFO("Connecting to the TunSafe Service...");
g_backend = CreateTunsafeServiceClient(g_backend_delegate);
}
g_backend->RequestStats(g_ui_visible);
}
static char *PrintMB(char *buf, int64 bytes) {
char *bo = buf;
if (bytes < 0) {
*buf++ = '-';
bytes = -bytes;
}
int64 big = bytes / (1024*1024);
int little = bytes % (1024*1024);
if (bytes < 10*1024*1024) {
// X.XXX
snprintf(buf, 64, "%lld.%.3d MB", big, 1000 * little / (1024*1024));
} else if (bytes < 100*1024*1024) {
// XX.XX
snprintf(buf, 64, "%lld.%.2d MB", big, 100 * little / (1024*1024));
} else {
// XX.X
snprintf(buf, 64, "%lld.%.1d MB", big, 10 * little / (1024*1024));
}
return bo;
}
static void UpdateIcon(UpdateIconWhy why) {
NOTIFYICONDATA nid;
memset(&nid, 0, sizeof(nid));
nid.cbSize = sizeof(nid);
nid.hWnd = g_ui_window;
nid.uID = 1;
nid.uVersion = NOTIFYICON_VERSION;
nid.uCallbackMessage = WM_USER + 1;
nid.uFlags = NIF_MESSAGE | NIF_TIP | NIF_ICON;
nid.hIcon = g_icons[g_is_connected_to_server ? 0 : 1];
char buf[kSizeOfAddress];
char namebuf[128];
if (g_is_connected_to_server) {
snprintf(nid.szTip, sizeof(nid.szTip), "TunSafe [%s - %s]", GetCurrentConfigTitle(namebuf, sizeof(namebuf)), print_ip(buf, g_backend->GetIP()));
if (!g_notified_connected_server) {
g_notified_connected_server = true;
nid.uFlags |= NIF_INFO;
snprintf(nid.szInfoTitle, sizeof(nid.szInfoTitle), "Connected to: %s", namebuf);
snprintf(nid.szInfo, sizeof(nid.szInfo), "IP: %s", buf);
nid.uTimeout = 5000;
nid.dwInfoFlags = NIIF_INFO;
}
} else {
g_notified_connected_server = false;
snprintf(nid.szTip, sizeof(nid.szTip), "TunSafe [%s]", "Disconnected");
if (why == UIW_STOPPED_WORKING_FAIL) {
nid.uFlags |= NIF_INFO;
strcpy(nid.szInfoTitle, "Disconnected!");
strcpy(nid.szInfo, "There was a problem with the connection. You are now disconnected.");
nid.uTimeout = 5000;
nid.dwInfoFlags = NIIF_ERROR;
}
}
Shell_NotifyIcon(g_has_icon ? NIM_MODIFY : NIM_ADD, &nid);
SendMessage(g_ui_window, WM_SETICON, ICON_SMALL, (LPARAM)g_icons[g_is_connected_to_server ? 0 : 1]);
g_has_icon = true;
}
static void RemoveIcon() {
if (g_has_icon) {
NOTIFYICONDATA nid;
memset(&nid, 0, sizeof(nid));
nid.cbSize = sizeof(nid);
nid.hWnd = g_ui_window;
nid.uID = 1;
Shell_NotifyIcon(NIM_DELETE, &nid);
}
}
#define MAX_CONFIG_FILES 1024
#define ID_POPUP_CONFIG_FILE 10000
char *config_filenames[MAX_CONFIG_FILES];
uint8 config_filenames_indent[MAX_CONFIG_FILES];
static char *StripConfExtension(const char *src, char *target, size_t size) {
size_t len = strlen(src);
if (len >= 5 && memcmp(src + len - 5, ".conf", 5) == 0)
len -= 5;
len = std::min(len, size - 1);
target[len] = 0;
memcpy(target, src, len);
return target;
}
static const char *GetCurrentConfigTitle(char *target, size_t size) {
const char *ll = FindFilenameComponent(g_current_filename);
return StripConfExtension(ll, target, size);
}
static void SetCurrentConfigFilename(const char *filename) {
str_set(&g_current_filename, filename);
char namebuf[64];
char *f = str_cat_alloc("TunSafe - ", GetCurrentConfigTitle(namebuf, sizeof(namebuf)));
SetWindowText(g_ui_window, f);
free(f);
InvalidateRect(hwndPaintBox, NULL, FALSE);
}
static void LoadConfigFile(const char *filename, bool save, bool force_start) {
SetCurrentConfigFilename(filename);
if (force_start || g_backend->is_started())
StartTunsafeBackend(UIW_START);
if (save)
RegWriteStr(g_reg_key, "ConfigFile", filename);
}
class ConfigMenuBuilder {
public:
ConfigMenuBuilder();
void Recurse();
int depth_;
int nfiles_;
size_t bufpos_;
WIN32_FIND_DATA wfd_;
char buf_[1024];
};
ConfigMenuBuilder::ConfigMenuBuilder()
: nfiles_(0), depth_(0) {
if (!GetConfigFullName("", buf_, sizeof(buf_)))
bufpos_ = sizeof(buf_);
else
bufpos_ = strlen(buf_);
}
void ConfigMenuBuilder::Recurse() {
if (bufpos_ >= sizeof(buf_) - 4)
return;
memcpy(buf_ + bufpos_, "*.*", 4);
HANDLE handle = FindFirstFile(buf_, &wfd_);
if (handle != INVALID_HANDLE_VALUE) {
do {
if (wfd_.cFileName[0] == '.')
continue;
size_t len = strlen(wfd_.cFileName);
if (bufpos_ + len >= sizeof(buf_) - 1)
continue;
size_t old_bufpos = bufpos_;
memcpy(buf_ + bufpos_, wfd_.cFileName, len + 1);
bufpos_ = bufpos_ + len + 1;
config_filenames_indent[nfiles_] = depth_ + !!(wfd_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
str_set(&config_filenames[nfiles_], buf_);
nfiles_++;
if (nfiles_ == MAX_CONFIG_FILES)
break;
if (wfd_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
buf_[bufpos_ - 1] = '\\';
depth_++;
if (depth_ < 16)
Recurse();
depth_--;
if (nfiles_ == MAX_CONFIG_FILES)
break;
}
bufpos_ = old_bufpos;
} while (FindNextFile(handle, &wfd_));
FindClose(handle);
}
}
static int AddToAvailableFilesPopup(HMENU menu, int max_num_items, bool is_settings) {
ConfigMenuBuilder menu_builder;
HMENU where[16] = {0};
menu_builder.Recurse();
bool is_connected = g_backend->is_started();
uint32 last_indent = 0;
where[0] = menu;
for (int i = 0; i < menu_builder.nfiles_; i++) {
uint32 indent = config_filenames_indent[i];
if (indent > last_indent) {
HMENU n = CreatePopupMenu();
where[indent] = n;
AppendMenu(where[last_indent], MF_POPUP, (UINT_PTR)n, FindFilenameComponent(config_filenames[i]));
} else {
bool selected_item = (strcmp(g_current_filename, config_filenames[i]) == 0);
AppendMenu(where[indent], (selected_item && is_connected) ?
MF_CHECKED : 0, ID_POPUP_CONFIG_FILE + i,
StripConfExtension(
FindFilenameComponent(config_filenames[i]), menu_builder.buf_, sizeof(menu_builder.buf_)));
if (selected_item)
SetMenuDefaultItem(where[indent], ID_POPUP_CONFIG_FILE + i, MF_BYCOMMAND);
}
last_indent = indent;
}
if (menu_builder.nfiles_ == 0)
AppendMenu(menu, MF_GRAYED | MF_DISABLED, 0, "(no config files found)");
return menu_builder.nfiles_;
}
static void ShowSettingsMenu(HWND wnd) {
HMENU menu = CreatePopupMenu();
AddToAvailableFilesPopup(menu, 10, true);
//POINT pt;
//GetCursorPos(&pt);
RECT r = GetParentRect(GetDlgItem(g_ui_window, ID_START));
RECT r2 = GetParentRect(hwndPaintBox);
POINT pt = {r2.left, r.bottom};
ClientToScreen(g_ui_window, &pt);
int rv = TrackPopupMenu(menu, 0, pt.x, pt.y, 0, wnd, NULL);
DestroyMenu(menu);
}
static bool HasReadWriteAccess(const char *filename) {
HANDLE fileH = CreateFile(filename,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, // For Exclusive access
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (fileH != INVALID_HANDLE_VALUE) {
CloseHandle(fileH);
return true;
}
return false;
}
static void OpenEditor() {
SHELLEXECUTEINFO shinfo = {0};
shinfo.hwnd = g_ui_window;
shinfo.cbSize = sizeof(shinfo);
shinfo.nShow = SW_SHOWNORMAL;
if (g_current_filename[0]) {
if (!HasReadWriteAccess(g_current_filename)) {
// Need to runas admin
char buf[1024];
if (!ExpandEnvironmentStrings("%windir%\\system32\\notepad.exe", buf, sizeof(buf)))
return;
shinfo.lpFile = buf;
char *filename = str_cat_alloc("\"", g_current_filename, "\"");
shinfo.lpParameters = filename;
shinfo.lpVerb = "runas";
ShellExecuteEx(&shinfo);
free(filename);
} else {
shinfo.fMask = SEE_MASK_CLASSNAME;
shinfo.lpFile = g_current_filename;
shinfo.lpParameters = "";
shinfo.lpClass = ".txt";
ShellExecuteEx(&shinfo);
}
}
}
static void BrowseFiles() {
char buf[MAX_PATH];
if (GetConfigFullName("", buf, ARRAYSIZE(buf))) {
size_t l = strlen(buf);
buf[l - 1] = 0;
ShellExecuteFromExplorer(buf, NULL, NULL, "explore");
}
}
bool ImportFile(const char *s, bool silent = false) {
char buf[1024];
char mesg[1024];
size_t filesize;
const char *last = FindFilenameComponent(s);
uint8 *filedata = NULL;
bool rv = false;
int filerv;
if (!*last || !GetConfigFullName(last, buf, ARRAYSIZE(buf)) || _stricmp(buf, s) == 0)
goto out;
filedata = LoadFileSane(s, &filesize);
if (!filedata)
goto out;
if (!silent) {
if (FileExists(buf)) {
snprintf(mesg, ARRAYSIZE(mesg), "A file already exists with the name '%s' in the configuration folder. Do you want to overwrite it?", last);
if (MessageBoxA(g_ui_window, mesg, "TunSafe", MB_OKCANCEL | MB_ICONEXCLAMATION) != IDOK)
goto out;
} else {
snprintf(mesg, ARRAYSIZE(mesg), "Do you want to import '%s' into TunSafe?", last);
if (MessageBoxA(g_ui_window, mesg, "TunSafe", MB_OKCANCEL | MB_ICONQUESTION) != IDOK)
goto out;
}
}
filerv = WriteOutFile(buf, filedata, filesize);
// elevate?
if (filerv == kWriteOutFile_AccessError && g_is_limited_uac_account) {
char *args = str_cat_alloc("--import \"", s, "\"");
rv = RunProcessAsAdminWithArgs(args, true);
free(args);
return rv;
}
rv = (filerv == kWriteOutFile_Ok);
if (!rv)
DeleteFileA(buf);
out:
free(filedata);
if (!silent) {
if (rv)
LoadConfigFile(buf, true, false);
else
MessageBoxA(g_ui_window, "There was a problem importing the file.", "TunSafe", MB_ICONEXCLAMATION);
}
return !rv;
}
void ShowUI(HWND hWnd) {
SetUiVisibility(true);
BringWindowToTop(hWnd);
SetForegroundWindow(hWnd);
}
void HandleDroppedFiles(HWND wnd, HDROP hdrop) {
char buf[MAX_PATH];
if (DragQueryFile(hdrop, -1, NULL, 0) == 1) {
if (DragQueryFile(hdrop, 0, buf, ARRAYSIZE(buf))) {
SetForegroundWindow(wnd);
ImportFile(buf);
}
}
DragFinish(hdrop);
}
void BrowseFile(HWND wnd) {
char szFile[1024];
// open a file name
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = g_ui_window;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "Config Files (*.conf)\0*.conf\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn))
ImportFile(szFile);
}
static const uint8 kCurve25519Basepoint[32] = {9};
static void SetKeyBox(HWND wnd, int ctr, uint8 buf[32]) {
uint8 *privs = base64_encode(buf, 32, NULL);
SetDlgItemText(wnd, ctr, (char*)privs);
free(privs);
}
static INT_PTR WINAPI KeyPairDlgProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) {
switch (message) {
case WM_INITDIALOG:
return TRUE;
case WM_CLOSE:
EndDialog(hWnd, 0);
return TRUE;
case WM_COMMAND:
switch (wParam) {
case IDCANCEL:
EndDialog(hWnd, 0);
return TRUE;
case IDC_PRIVATE_KEY | (EN_CHANGE << 16) : {
char buf[128];
uint8 pub[32];
uint8 priv[32];
buf[0] = 0;
size_t len = GetDlgItemText(hWnd, IDC_PRIVATE_KEY, buf, sizeof(buf));
size_t olen = 32;
if (base64_decode((uint8*)buf, len, priv, &olen) && olen == 32) {
curve25519_donna(pub, priv, kCurve25519Basepoint);
SetKeyBox(hWnd, IDC_PUBLIC_KEY, pub);
} else {
SetDlgItemText(hWnd, IDC_PUBLIC_KEY, "(Invalid Private Key)");
}
return TRUE;
}
case IDRAND: {
uint8 priv[32];
uint8 pub[32];
OsGetRandomBytes(priv, 32);
curve25519_normalize(priv);
curve25519_donna(pub, priv, kCurve25519Basepoint);
SetKeyBox(hWnd, IDC_PRIVATE_KEY, priv);
SetKeyBox(hWnd, IDC_PUBLIC_KEY, pub);
return TRUE;
}
}
}
return FALSE;
}
static void SetStartupFlags(int new_flags) {
// Determine whether to autorun or not.
bool autorun = (new_flags & kStartupFlag_MinimizeToTrayWhenWindowsStarts) ||
!(new_flags & kStartupFlag_BackgroundService) && (new_flags & kStartupFlag_ConnectWhenWindowsStarts);
// Update the autorun key.
HKEY hkey;
LSTATUS result;
result = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hkey);
if (result == 0) {
if (autorun) {
wchar_t buf[512 + 32];
buf[0] = '"';
DWORD len = GetModuleFileNameW(NULL, buf + 1, 512);
if (len < 512) {
memcpy(buf + len + 1, L"\" --autostart", sizeof(wchar_t) * 14);
result = RegSetValueExW(hkey, L"TunSafe", NULL, REG_SZ, (BYTE*)buf, (DWORD)(len + 15) * sizeof(wchar_t));
}
} else {
RegDeleteValueW(hkey, L"TunSafe");
}
RegCloseKey(hkey);
}
RegWriteInt(g_reg_key, "StartupFlags", new_flags);
bool was_started = g_backend && g_backend->is_started();
bool recreate_backend = false;
if (!!(new_flags & (kStartupFlag_BackgroundService | kStartupFlag_ForegroundService))) {
// Want to run as a service - make sure service is installed and running.
if (!IsTunsafeServiceRunning()) {
g_backend->Stop();
RINFO("Starting TunSafe service...");
InstallTunSafeWindowsService();
recreate_backend = true;
}
} else {
if (IsTunSafeServiceInstalled()) {
g_backend->Stop();
g_backend->Teardown();
RINFO("Removing TunSafe service...");
// Don't want to run as a service - Make sure we delete the service.
if (g_is_limited_uac_account) {
// Need to stop this early so service process is able to open.
CloseHandle(g_runonce_mutex);
if (!RunProcessAsAdminWithArgs("--delete-service-and-start", false)) {
RINFO("Unable to stop and remove service");
uint32 m = kStartupFlag_BackgroundService | kStartupFlag_ForegroundService;
new_flags = (g_startup_flags & m) | (new_flags & ~m);
} else {
PostQuitMessage(0);
return;
}
} else {
if (!UninstallTunSafeWindowsService()) {
RINFO("Unable to stop and remove service");
uint32 m = kStartupFlag_BackgroundService | kStartupFlag_ForegroundService;
new_flags = (g_startup_flags & m) | (new_flags & ~m);
}
}
recreate_backend = true;
}
}
if (recreate_backend) {
CreateLocalOrRemoteBackend(!!(new_flags & (kStartupFlag_BackgroundService | kStartupFlag_ForegroundService)));
if (was_started)
StartTunsafeBackend(UIW_START);
}
g_startup_flags = new_flags;
g_backend->SetServiceStartupFlags(g_startup_flags);
}
enum {
kTab_Logs = 0,
kTab_Charts = 1,
kTab_Advanced = 2,
};
static void UpdateGraphReq() {
if (g_backend && (g_current_tab != 1 || !g_ui_visible))
g_backend->GetGraph(0);
}
static void UpdateTabSelection() {
int tab = TabCtrl_GetCurSel(hwndTab);
HWND wnd = g_ui_window;
g_current_tab = tab;
ShowWindow(hwndEdit, (tab == kTab_Logs) ? SW_SHOW : SW_HIDE);
ShowWindow(hwndGraphBox, (tab == kTab_Charts) ? SW_SHOW : SW_HIDE);
ShowWindow(hwndAdvancedBox, (tab == kTab_Advanced) ? SW_SHOW : SW_HIDE);
UpdateGraphReq();
}
struct WindowSizingItem {
uint16 id;
uint16 edges;
};
enum {
WSI_LEFT = 1,
WSI_RIGHT = 2,
WSI_TOP = 4,
WSI_BOTTOM = 8,
};
static const WindowSizingItem kWindowSizing[] = {
{ID_START,WSI_LEFT | WSI_RIGHT},
{ID_STOP,WSI_LEFT | WSI_RIGHT},
{ID_EDITCONF,WSI_LEFT | WSI_RIGHT},
{IDC_PAINTBOX,WSI_RIGHT},
{IDC_TAB, WSI_RIGHT | WSI_BOTTOM},
};
static void HandleWindowSizing() {
RECT wr;
GetClientRect(g_ui_window, &wr);
static int g_orig_w, g_orig_h;
static RECT g_orig_rects[ARRAYSIZE(kWindowSizing)];
if (g_orig_w == 0) {
g_orig_w = wr.right;
g_orig_h = wr.bottom;
for (size_t i = 0; i < ARRAYSIZE(kWindowSizing); i++) {
const WindowSizingItem *it = &kWindowSizing[i];
g_orig_rects[i] = GetParentRect(GetDlgItem(g_ui_window, it->id));
}
}
int dx = wr.right - g_orig_w;
int dy = wr.bottom - g_orig_h;
if (dx|dy) {
HDWP dwp = BeginDeferWindowPos(10), dwp_next;
for (size_t i = 0; i < ARRAYSIZE(kWindowSizing); i++) {
const WindowSizingItem *it = &kWindowSizing[i];
HWND wnd = GetDlgItem(g_ui_window, it->id);
RECT r = g_orig_rects[i];
if (it->edges & WSI_LEFT) r.left += dx;
if (it->edges & WSI_RIGHT) r.right += dx;
if (it->edges & WSI_TOP) r.top += dy;
if (it->edges & WSI_BOTTOM) r.bottom += dy;
if (r.right < r.left) r.right = r.left;
if (r.bottom < r.top) r.bottom = r.top;
dwp_next = DeferWindowPos(dwp, wnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOACTIVATE);
dwp = dwp_next ? dwp_next : dwp;
}
EndDeferWindowPos(dwp);
}
RECT rect = GetParentRect(hwndTab);
TabCtrl_AdjustRect(hwndTab, false, &rect);
MoveWindow(hwndEdit, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
MoveWindow(hwndGraphBox, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
MoveWindow(hwndAdvancedBox, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
int parts[3] = {
(int)(wr.right * 0.2f),
(int)(wr.right * 0.6f),
(int)-1,
};
SendMessage(hwndStatus, SB_SETPARTS, 3, (LPARAM)parts);
SendMessage(hwndStatus, WM_SIZE, 0, 0);
InvalidateRect(hwndStatus, NULL, TRUE);
}
static void HandleClickedItem(HWND hWnd, int wParam) {
if (wParam >= ID_POPUP_CONFIG_FILE && wParam < ID_POPUP_CONFIG_FILE + MAX_CONFIG_FILES) {
const char *new_conf = config_filenames[wParam - ID_POPUP_CONFIG_FILE];
if (!new_conf)
return;
if (strcmp(new_conf, g_current_filename) == 0 && g_backend->is_started()) {
StopTunsafeBackend(UIW_NONE);
} else {
LoadConfigFile(new_conf, true, GetAsyncKeyState(VK_SHIFT) >= 0);
}
return;
}
switch (wParam) {
case ID_START: StartTunsafeBackend(UIW_START); break;
case ID_STOP: StopTunsafeBackend(UIW_NONE); break;
case ID_EXIT: PostQuitMessage(0); break;
case ID_MORE_BUTTON: ShowSettingsMenu(hWnd); break;
case IDSETT_WEB_PAGE: ShellExecute(g_ui_window, NULL, "https://tunsafe.com/", NULL, NULL, 0); break;
case IDSETT_OPENSOURCE: ShellExecute(g_ui_window, NULL, "https://tunsafe.com/open-source", NULL, NULL, 0); break;
case ID_EDITCONF: OpenEditor(); break;
case IDSETT_BROWSE_FILES:BrowseFiles(); break;
case IDSETT_OPEN_FILE: BrowseFile(hWnd); break;
case IDSETT_ABOUT:
MessageBoxA(g_ui_window, TUNSAFE_VERSION_STRING "\r\n\r\nCopyright <20> 2018, Ludvig Strigeus\r\n\r\nThanks for choosing TunSafe!\r\n\r\nThis version was built on " __DATE__ " " __TIME__, "About TunSafe", MB_ICONINFORMATION);
break;
case IDSETT_KEYPAIR:
DialogBox(g_hinstance, MAKEINTRESOURCE(IDD_DIALOG2), hWnd, &KeyPairDlgProc);
break;
case IDSETT_BLOCKINTERNET_OFF:
case IDSETT_BLOCKINTERNET_ROUTE:
case IDSETT_BLOCKINTERNET_FIREWALL:
case IDSETT_BLOCKINTERNET_BOTH:
{
InternetBlockState old_state = g_backend->GetInternetBlockState(NULL);
InternetBlockState new_state = (InternetBlockState)(wParam - IDSETT_BLOCKINTERNET_OFF);
if (old_state == kBlockInternet_Off && new_state != kBlockInternet_Off) {
if (MessageBoxA(g_ui_window, "Warning! All Internet traffic will be blocked until you restart your computer. Only traffic through TunSafe will be allowed.\r\n\r\nThe blocking is activated the next time you connect to a VPN server.\r\n\r\nDo you want to continue?", "TunSafe", MB_ICONWARNING | MB_OKCANCEL) == IDCANCEL)
return;
}
g_backend->SetInternetBlockState(new_state);
if ((~old_state & new_state) && g_backend->is_started())
StartTunsafeBackend(UIW_START);
return;
}
case IDSETT_SERVICE_OFF:
case IDSETT_SERVICE_FOREGROUND:
case IDSETT_SERVICE_BACKGROUND:
SetStartupFlags((int)((g_startup_flags & ~3) + wParam - IDSETT_SERVICE_OFF));
break;
case IDSETT_SERVICE_CONNECT_AUTO:
SetStartupFlags(g_startup_flags ^ kStartupFlag_ConnectWhenWindowsStarts);
break;
case IDSETT_SERVICE_MINIMIZE_AUTO:
SetStartupFlags(g_startup_flags ^ kStartupFlag_MinimizeToTrayWhenWindowsStarts);
break;
case IDSETT_PREPOST:
{
if (!g_hklm_reg_key) {
if (!RunProcessAsAdminWithArgs(g_allow_pre_post ? "--set-allow-pre-post 0" : "--set-allow-pre-post 1", true))
MessageBox(g_ui_window, "You need to run TunSafe as an Administrator to be able to change this setting.", "TunSafe", MB_ICONWARNING);
g_allow_pre_post = RegReadInt(g_hklm_readonly_reg_key, "AllowPrePost", 0) != 0;
return;
}
g_allow_pre_post = !g_allow_pre_post;
RegWriteInt(g_hklm_reg_key, "AllowPrePost", g_allow_pre_post);
return;
}
}
}
static INT_PTR WINAPI DlgProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) {
switch (message) {
case WM_INITDIALOG:
SetMenu(hWnd, LoadMenu(g_hinstance, MAKEINTRESOURCE(IDR_MENU1)));
return TRUE;
case WM_CLOSE:
SetUiVisibility(false);
return TRUE;
case WM_NOTIFY: {
UINT idFrom = (UINT)((NMHDR*)lParam)->idFrom;
switch (((NMHDR*)lParam)->code) {
case TCN_SELCHANGE:
switch (idFrom) {
case IDC_TAB:
UpdateTabSelection();
return TRUE;
}
break;
}
break;
}
case WM_COMMAND:
switch (HIWORD(wParam)) {
case 0:
HandleClickedItem(hWnd, (int)wParam);
break;
}
break;
case WM_DROPFILES:
if (!wm_dropfiles_recursive) {
wm_dropfiles_recursive = true;
HandleDroppedFiles(hWnd, (HDROP)wParam);
wm_dropfiles_recursive = false;
}
break;
case WM_USER + 1:
if (lParam == WM_RBUTTONUP) {
HMENU menu = CreatePopupMenu();
if (AddToAvailableFilesPopup(menu, 10, false))
AppendMenu(menu, MF_SEPARATOR, 0, 0);
bool active = g_backend->is_started();
AppendMenu(menu, 0, ID_START, active ? "Re&connect" : "&Connect");
AppendMenu(menu, active ? 0 : MF_GRAYED, ID_STOP, "&Disconnect");
AppendMenu(menu, MF_SEPARATOR, 0, NULL);
AppendMenu(menu, 0, ID_EXIT, "&Exit");
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(hWnd);
int rv = TrackPopupMenu(menu, 0, pt.x, pt.y, 0, hWnd, NULL);
DestroyMenu(menu);
} else if (lParam == WM_LBUTTONDBLCLK) {
if (IsWindowVisible(hWnd)) {
SetUiVisibility(false);
} else {
ShowUI(hWnd);
}
}
return TRUE;
case WM_USER + 2:
g_backend_delegate->DoWork();
return true;
case WM_INITMENU: {
HMENU menu = GetMenu(g_ui_window);
CheckMenuItem(menu, IDSETT_SERVICE_CONNECT_AUTO, MF_CHECKED * !!(g_startup_flags & kStartupFlag_ConnectWhenWindowsStarts));
CheckMenuItem(menu, IDSETT_SERVICE_MINIMIZE_AUTO, MF_CHECKED * !!(g_startup_flags & kStartupFlag_MinimizeToTrayWhenWindowsStarts));
CheckMenuItem(menu, IDSETT_PREPOST, g_allow_pre_post ? MF_CHECKED : 0);
bool is_activated = false;
int value = g_backend->GetInternetBlockState(&is_activated);
CheckMenuRadioItem(menu, IDSETT_BLOCKINTERNET_OFF, IDSETT_BLOCKINTERNET_BOTH, IDSETT_BLOCKINTERNET_OFF + value, MF_BYCOMMAND);
CheckMenuRadioItem(menu, IDSETT_SERVICE_OFF, IDSETT_SERVICE_BACKGROUND, IDSETT_SERVICE_OFF + (g_startup_flags & 3), MF_BYCOMMAND);
break;
}
case WM_SIZE:
if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {
if (g_ui_window)
HandleWindowSizing();
}
break;
case WM_EXITMENULOOP:
g_timestamp_of_exit_menuloop = GetTickCount();
break;
default:
if (message == g_message_taskbar_created) {
g_has_icon = false;
UpdateIcon(UIW_NONE);
}
break;
}
return FALSE;
}
void PushLine(const char *s) {
size_t l = strlen(s);
char buf[64];
SYSTEMTIME t;
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);
if (!x) return;
memcpy(x, buf, tl);
memcpy(x + tl, s, l);
x[l + tl] = '\r';
x[l + tl + 1] = '\n';
x[l + tl + 2] = '\0';
g_backend_delegate->OnLogLine((const char**)&x);
free(x);
}
void EnsureConfigDirCreated() {
char fullname[1024];
if (GetConfigFullName("", fullname, sizeof(fullname)))
CreateDirectory(fullname, NULL);
}
void EnableControl(int wnd, bool b) {
EnableWindow(GetDlgItem(g_ui_window, wnd), b);
}
LRESULT CALLBACK NotifyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_USER + 10:
if (wParam == 1) {
PostQuitMessage(0);
return 31337;
} else if (wParam == 0) {
ShowUI(g_ui_window);
return 31337;
}
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void CreateNotificationWindow() {
WNDCLASSEX wce = {0};
wce.cbSize = sizeof(wce);
wce.lpfnWndProc = &NotifyWndProc;
wce.hInstance = g_hinstance;
wce.lpszClassName = "TunSafe-f19e092db01cbe0fb6aee132f8231e5b71c98f90";
RegisterClassEx(&wce);
CreateWindow("TunSafe-f19e092db01cbe0fb6aee132f8231e5b71c98f90", "TunSafe-f19e092db01cbe0fb6aee132f8231e5b71c98f90", 0, 0, 0, 0, 0, 0, 0, g_hinstance, NULL);
}
HFONT CreateBoldUiFont() {
LOGFONT lf;
HFONT ffont = (HFONT)SendMessage(g_ui_window, WM_GETFONT, 0, 0);
GetObject(ffont, sizeof(lf), &lf);
lf.lfWeight = FW_BOLD;
HFONT font = CreateFontIndirect(&lf);
return font;
}
void FillRectColor(HDC dc, const RECT &r, COLORREF color) {
COLORREF old = ::SetBkColor(dc, color);
ExtTextOut(dc, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL);
::SetBkColor(dc, old);
}
void DrawRectOutline(HDC dc, const RECT &r) {
POINT points[5] = {
{r.left, r.top},
{r.right, r.top},
{r.right, r.bottom},
{r.left, r.bottom},
{r.left, r.top}
};
Polyline(dc, points, 5);
}
static HFONT CreateFontHelper(int size, byte flags, const char *face, int angle = 0) {
return CreateFontA(-RescaleDpi(size), 0, angle, angle, flags & 1 ? FW_BOLD : 0, FALSE, flags & 2 ? 1 : 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, face);
}
static const char *StatusCodeToString(TunsafeBackend::StatusCode code) {
switch (code) {
case TunsafeBackend::kErrorInitialize: return "Configuration Error";
case TunsafeBackend::kErrorTunPermanent: return "TUN Adapter Error";
case TunsafeBackend::kErrorServiceLost: return "Service Lost";
case TunsafeBackend::kStatusStopped: return "Disconnected";
case TunsafeBackend::kStatusInitializing: return "Initializing";
case TunsafeBackend::kStatusConnecting: return "Connecting...";
case TunsafeBackend::kStatusReconnecting: return "Reconnecting...";
case TunsafeBackend::kStatusConnected: return "Connected";
case TunsafeBackend::kStatusTunRetrying: return "TUN Adapter Error, retrying...";
default:
return "Unknown";
}
}
static void DrawInPaintBox(HDC hdc, int w, int h) {
RECT rect = {0, 0, w, h};
FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE + 1));
HFONT font = CreateBoldUiFont();
char namebuf[128];
GetCurrentConfigTitle(namebuf, sizeof(namebuf));
RECT btrect = GetParentRect(GetDlgItem(g_ui_window, ID_START));
HPEN pen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_3DSHADOW));
HBRUSH brush = GetSysColorBrush(COLOR_WINDOW);
SelectObject(hdc, pen);
SelectObject(hdc, brush);
comborect = MakeRect(0, btrect.top + 1, w, btrect.bottom - 1);
Rectangle(hdc, 0, btrect.top + 1, w, btrect.bottom - 1);
if (arrowbitmap == NULL)
arrowbitmap = LoadBitmap(g_hinstance, MAKEINTRESOURCE(IDB_DOWNARROW));
int bw = RescaleDpi(6);
HDC memdc = CreateCompatibleDC(hdc);
SelectObject(memdc, arrowbitmap);
StretchBlt(hdc, w - 1 - bw - 5, btrect.top + 1 + ((btrect.bottom - btrect.top - bw) >> 1),
bw, bw, memdc, 0, 0, 6, 6, SRCCOPY);
int th = RescaleDpi(20);
SelectObject(hdc, font);
SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
TextOut(hdc, RescaleDpi(4), btrect.top + RescaleDpi(4), namebuf, (int)strlen(namebuf));
int y = btrect.bottom + RescaleDpi(4);
DeleteObject(pen);
SelectObject(hdc, (HFONT)SendMessage(g_ui_window, WM_GETFONT, 0, 0));
SetBkColor(hdc, GetSysColor(COLOR_3DFACE));
TunsafeBackend::StatusCode status = g_backend->status();
my_strlcpy(namebuf, sizeof(namebuf) - 32, StatusCodeToString(status));
if (status == TunsafeBackend::kStatusConnected || status == TunsafeBackend::kStatusReconnecting) {
uint64 when = g_processor_stats.first_complete_handshake_timestamp;
uint32 seconds = (when != 0) ? (uint32)((OsGetMilliseconds() - when + 500) / 1000) : 0;
snprintf(strchr(namebuf, 0), 32, ", %.2d:%.2d:%.2d", seconds / 3600, (seconds / 60) % 60, seconds % 60);
}
int img = (status == TunsafeBackend::kStatusConnected) ? 0 :
g_backend->is_started() && !TunsafeBackend::IsPermanentError(status) ? 1 : 2;
static const COLORREF kDotColors[3] = {
0x51a600,
0x00c0c0,
0x0000c0,
};
SetBkMode(hdc, TRANSPARENT);
COLORREF oldcolor = SetTextColor(hdc, kDotColors[img]);
HFONT oldfont = (HFONT)SelectObject(hdc, CreateFontHelper(18, 0, "Tahoma"));
wchar_t bullet = 0x25CF;
TextOutW(hdc, RescaleDpi(2), y - RescaleDpi(7), &bullet, 1);
DeleteObject(SelectObject(hdc, oldfont));
SetTextColor(hdc, oldcolor);
TextOut(hdc, RescaleDpi(2 + 14), y, namebuf, (int)strlen(namebuf));
y += RescaleDpi(18);
uint32 ip = g_backend->GetIP();
if (ip) {
print_ip(namebuf, ip);
TextOut(hdc, 2, y, namebuf, (int)strlen(namebuf));
}
DeleteObject(font);
DeleteDC(memdc);
}
typedef void DrawInPaintBoxFunc(HDC dc, int w, int h);
static void HandleWmPaintPaintbox(HWND hwnd, DrawInPaintBoxFunc *func) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
RECT r;
GetClientRect(hwnd, &r);
HBITMAP bmp = CreateCompatibleBitmap(ps.hdc, r.right, r.bottom);
HDC dc = CreateCompatibleDC(ps.hdc);
SelectObject(dc, bmp);
func(dc, r.right, r.bottom);
BitBlt(ps.hdc, 0, 0, r.right, r.bottom, dc, 0, 0, SRCCOPY);
DeleteDC(dc);
DeleteObject(bmp);
EndPaint(hwnd, &ps);
}
static LRESULT CALLBACK PaintBoxWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
HandleWmPaintPaintbox(hwnd, &DrawInPaintBox);
return TRUE;
}
case WM_LBUTTONDOWN: {
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
if (PtInRect(&comborect, pt)) {
// Avoid showing the menu again if clicking to close.
if (GetTickCount() - g_timestamp_of_exit_menuloop >= 50u)
ShowSettingsMenu(g_ui_window);
}
return TRUE;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
static void DrawGraph(HDC dc, const RECT *rr, StatsCollector::TimeSeries **sources, const COLORREF *colors, int num_source, const char *xcaption, const char *ycaption) {
RECT r = *rr;
FillRectColor(dc, r, 0xffffff);
RECT margins = { 30, 10, -10, -15 };
margins = RescaleDpiRect(margins);
r.left += margins.left;
r.top += margins.top;
r.right += margins.right;
r.bottom += margins.bottom;
HPEN borderpen = CreatePen(PS_SOLID, 1, 0x808080);
SelectObject(dc, borderpen);
DrawRectOutline(dc, r);
static const uint8 bits[4] = {0x70, 0, 0, 0};
HBITMAP bmp = CreateBitmap(4, 1, 1, 1, &bits);
HBRUSH brush = CreatePatternBrush(bmp);
DeleteObject(bmp);
// Draw horizontal dotted lines
{
SetTextColor(dc, 0x808080);
SetBkColor(dc, 0xffffff);
int inc = (r.bottom - r.top) >> 2;
RECT r2 = {r.left + 1, r.top + inc * 1, r.right - 1, r.top + inc * 1 + 1};
FillRect(dc, &r2, brush);
r2.top += inc; r2.bottom += inc;
FillRect(dc, &r2, brush);
r2.top += inc; r2.bottom += inc;
FillRect(dc, &r2, brush);
}
DeleteObject(brush);
static const uint8 bits_vertical[16] = {
0xff, 0x0, 0xff, 0,
0xff, 0x0, 0x0, 0,
0xff, 0x0, 0x0, 0,
0x0, 0x0, 0x0, 0};
bmp = CreateBitmap(1, 4, 1, 1, &bits_vertical);
brush = CreatePatternBrush(bmp);
DeleteObject(bmp);
{
// Draw vertical dotted lines
for (int i = 1; i < 12; i++) {
int x = (r.right - r.left) * i / 12;
RECT r2 = {r.left + x, r.top + 1, r.left + x + 1, r.bottom - 1};
FillRect(dc, &r2, brush);
}
}
{
// Draw legend text
HFONT font = CreateFontHelper(10, 0, "Tahoma");
SelectObject(dc, font);
SetTextColor(dc, 0x202020);
SetBkMode(dc, TRANSPARENT);
RECT r2 = {r.left + 1, r.bottom, r.right - 1, r.bottom + RescaleDpi(15)};
DrawText(dc, xcaption, (int)strlen(xcaption), &r2, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DeleteObject(font);
}
DeleteObject(brush);
DeleteObject(borderpen);
// Determine the scaling factor
float mx = 1;
for (size_t j = 0; j != num_source; j++) {
const StatsCollector::TimeSeries *src = sources[j];
for (size_t i = 0; i != src->size; i++)
mx = max(mx, src->data[i]);
}
int topval = (int)(mx + 0.5f);
// round it appropriately
if (topval >= 500)
topval = (topval + 99) / 100 * 100;
else if (topval >= 200)
topval = (topval + 49) / 50 * 50;
else if (topval >= 50)
topval = (topval + 9) / 10 * 10;
else if (topval >= 20)
topval = (topval + 4) / 5 * 5;
if (topval > mx)
mx = (float)topval;
{
RECT r2 = {r.left - RescaleDpi(30), r.top - RescaleDpi(2), r.left - RescaleDpi(2), r.bottom};
char buf[30];
sprintf(buf, "%d", topval);
DrawText(dc, buf, (int)strlen(buf), &r2, DT_RIGHT | DT_SINGLELINE);
r2.top = r.bottom - RescaleDpi(12);
DrawText(dc, "0", 1, &r2, DT_RIGHT | DT_SINGLELINE);
}
float mx_f = (1.0f / mx) * (r.bottom - r.top);
for (size_t k = 0; k != num_source; k++) {
HPEN borderpen = CreatePen(PS_SOLID, 2, colors[k]);
SelectObject(dc, borderpen);
const StatsCollector::TimeSeries *src = sources[k];
POINT *points = new POINT[src->size];
for (size_t i = 0, j = src->shift; i != src->size; i++) {
points[i].x = (int)(r.left + (r.right - r.left) * i / (src->size - 1));
points[i].y = r.bottom - (int)((float)src->data[j] * mx_f);
if (++j == src->size) j = 0;
}
Polyline(dc, points, src->size);
delete points;
DeleteObject(borderpen);
}
if (ycaption != NULL) {
HFONT font = CreateFontHelper(10, 0, "Tahoma", 900);
SelectObject(dc, font);
TextOut(dc, r.left - RescaleDpi(18), ((r.top + r.bottom) >> 1) + RescaleDpi(12), ycaption, (int)strlen(ycaption));
DeleteObject(font);
}
}
static const char * const kGraphStepNames[] = {
"1 second step",
"5 second step",
"30 second step",
"5 minute step",
};
static void DrawInGraphBox(HDC hdc, int w, int h) {
RECT r = {0, 0, w, h};
static const COLORREF color[4] = {
0x00c000,
0xc00000,
};
LinearizedGraph *graph = g_backend->GetGraph(g_selected_graph_type);
StatsCollector::TimeSeries *time_series_ptr[4];
StatsCollector::TimeSeries time_series[4];
int num_charts = 0;
if (graph && graph->num_charts <= 4) {
uint8 *ptr = (uint8*)(graph + 1);
for (int i = 0; i < graph->num_charts; i++) {
time_series_ptr[i] = &time_series[i];
time_series[i].shift = 0;
time_series[i].size = *(uint32*)ptr;
time_series[i].data = (float*)(ptr + 4);
ptr += 4 + *(uint32*)ptr * 4;
if (ptr - (uint8*)graph > graph->total_size)
break;
}
num_charts = graph->num_charts;
}
char buf[256];
snprintf(buf, sizeof(buf), "Time (%s)", kGraphStepNames[g_selected_graph_type]);
DrawGraph(hdc, &r, time_series_ptr, color, num_charts, buf, "Mbps");
free(graph);
}
static LRESULT CALLBACK GraphBoxWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
HandleWmPaintPaintbox(hwnd, &DrawInGraphBox);
return TRUE;
}
case WM_RBUTTONDOWN: {
HMENU menu = CreatePopupMenu();
for(int i = 0; i < ARRAYSIZE(kGraphStepNames); i++)
AppendMenu(menu, (i == g_selected_graph_type) * MF_CHECKED, i + 1, kGraphStepNames[i]);
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
ClientToScreen(hwnd, &pt);
int rv = TrackPopupMenu(menu, TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
DestroyMenu(menu);
if (rv != 0) {
g_selected_graph_type = rv - 1;
InvalidateRect(hwnd, NULL, FALSE);
}
return TRUE;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
struct AdvancedTextInfo {
uint16 y;
uint8 indent;
const char *title;
};
static const AdvancedTextInfo ADVANCED_TEXT_INFOS[] = {
#define Y 26
{Y + 19 * 0, 66, "Public Key:"},
{Y + 19 * 1, 66, "Endpoint:"},
{Y + 19 * 2, 66, "Transfer:"},
{Y + 19 * 3, 66, "Handshake:"},
{Y + 19 * 4, 66, ""},
{Y + 19 * 5, 66, "Overhead:"},
#undef Y
};
static char *PrintLastHandshakeAt(char buf[256], WgProcessorStats *ps) {
char *d = buf;
if (ps->last_complete_handshake_timestamp) {
uint32 ago = (uint32)((OsGetMilliseconds() - ps->last_complete_handshake_timestamp + 500) / 1000);
uint32 hours = ago / 3600;
uint32 minutes = (ago - hours * 3600) / 60;
uint32 seconds = (ago - hours * 3600 - minutes * 60);
if (hours)
d += snprintf(d, 32, hours == 1 ? "%d hour, " : "%d hours, ", hours);
if (minutes)
d += snprintf(d, 32, minutes == 1 ? "%d minute, " : "%d minutes, ", minutes);
if (d == buf || seconds)
d += snprintf(d, 32, seconds == 1 ? "%d second, " : "%d seconds, ", seconds);
memcpy(d - 2, " ago", 5);
} else {
memcpy(buf, "(never)", 8);
}
return buf;
}
static const char *GetAdvancedInfoValue(char buffer[256], int i) {
char tmp[64], tmp2[64];
WgProcessorStats *ps = &g_processor_stats;
switch (i) {
case 0: {
if (IsOnlyZeros(g_backend->public_key(), 32))
return "";
char *str = (char*)base64_encode(g_backend->public_key(), 32, NULL);
snprintf(buffer, 256, "%s", str);
free(str);
return buffer;
}
case 1: {
char ip[kSizeOfAddress];
if (ps->endpoint.sin.sin_family == 0)
return "";
PrintIpAddr(ps->endpoint, ip);
snprintf(buffer, 256, "%s:%d", ip, htons(ps->endpoint.sin.sin_port));
return buffer;
}
case 2:
snprintf(buffer, 256, "%s in (%lld packets), %s out (%lld packets)",
PrintMB(tmp, ps->udp_bytes_in), ps->udp_packets_in,
PrintMB(tmp2, ps->udp_bytes_out), ps->udp_packets_out/*, udp_qsize2 - udp_qsize1, g_tun_reads*/);
return buffer;
case 3: return PrintLastHandshakeAt(buffer, ps);
case 4: {
snprintf(buffer, 256, "%d handshakes in (%d failed), %d handshakes out (%d failed)",
ps->handshakes_in, ps->handshakes_in - ps->handshakes_in_success,
ps->handshakes_out, ps->handshakes_out - ps->handshakes_out_success);
return buffer;
}
case 5: {
uint64 overhead_in = ps->udp_bytes_in + ps->udp_packets_in * 40 - ps->tun_bytes_out;
uint32 overhead_in_pct = ps->tun_bytes_out ? (uint32)(overhead_in * 100000 / ps->tun_bytes_out) : 0;
uint64 overhead_out = ps->udp_bytes_out + ps->udp_packets_out * 40 - ps->tun_bytes_in;
uint32 overhead_out_pct = ps->tun_bytes_in ? (uint32)(overhead_out * 100000 / ps->tun_bytes_in) : 0;
snprintf(buffer, 256, "%d.%.3d%% in, %d.%.3d%% out", overhead_in_pct / 1000, overhead_in_pct % 1000,
overhead_out_pct / 1000, overhead_out_pct % 1000);
return buffer;
}
default: return "";
}
}
static void DrawInAdvancedBox(HDC dc, int w, int h) {
RECT r = {0, 0, w, h};
FillRectColor(dc, r, 0xffffff);
SelectObject(dc, (HFONT)SendMessage(g_ui_window, WM_GETFONT, 0, 0));
SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
SetBkColor(dc, GetSysColor(COLOR_WINDOW));
const AdvancedTextInfo *tp = ADVANCED_TEXT_INFOS;
char buffer[256];
for (size_t i = 0; i != ARRAYSIZE(ADVANCED_TEXT_INFOS); i++, tp++) {
int x = 8;
RECT r = {x, tp->y, x + tp->indent, tp->y + 19};
r = RescaleDpiRect(r);
::ExtTextOut(dc, r.left, r.top, ETO_CLIPPED | ETO_OPAQUE, &r, tp->title, (UINT)strlen(tp->title), NULL);
const char *s = GetAdvancedInfoValue(buffer, (int)i);
r.left = r.right;
r.right = w;
::ExtTextOut(dc, r.left, r.top, ETO_CLIPPED | ETO_OPAQUE, &r, s, (UINT)strlen(s), NULL);
}
SetBkColor(dc, GetSysColor(COLOR_3DFACE));
static const int grouptop[1] = {
2
};
static const char *grouptext[1] = {
"General",
};
HFONT font = CreateFontHelper(12, 1, "Tahoma");
SelectObject(dc, font);
for (size_t i = 0; i != ARRAYSIZE(grouptext); i++) {
RECT r = {RescaleDpi(4), RescaleDpi(grouptop[i]), w - RescaleDpi(4), RescaleDpi(grouptop[i] + 18)};
::ExtTextOut(dc, RescaleDpi(8), r.top + 1, ETO_CLIPPED | ETO_OPAQUE, &r, grouptext[i], (UINT)strlen(grouptext[i]), NULL);
}
DeleteFont(font);
}
static LRESULT CALLBACK AdvancedBoxWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
HandleWmPaintPaintbox(hwnd, &DrawInAdvancedBox);
return TRUE;
}
case WM_ERASEBKGND:
return TRUE;
case WM_RBUTTONDOWN: {
int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
char buffer[256];
const AdvancedTextInfo *tp = ADVANCED_TEXT_INFOS;
for (size_t i = 0; i != ARRAYSIZE(ADVANCED_TEXT_INFOS); i++, tp++) {
if (x >= RescaleDpi(tp->indent) && y >= RescaleDpi(tp->y) && y < RescaleDpi(tp->y + 19)) {
HMENU menu = CreatePopupMenu();
AppendMenu(menu, 0, 1, "Copy");
POINT pt = {x, y};
ClientToScreen(hwnd, &pt);
int rv = TrackPopupMenu(menu, TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
DestroyMenu(menu);
if (rv == 1)
SetClipboardString(GetAdvancedInfoValue(buffer, (int)i));
return TRUE;
}
}
return TRUE;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void InitializeClass(WNDPROC wndproc, const char *name) {
WNDCLASSEX wce = {0};
wce.cbSize = sizeof(wce);
wce.lpfnWndProc = wndproc;
wce.hInstance = g_hinstance;
wce.lpszClassName = name;
wce.style = CS_HREDRAW | CS_VREDRAW;
wce.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClassEx(&wce);
}
static bool CreateMainWindow() {
LoadLibrary(TEXT("Riched20.dll"));
INITCOMMONCONTROLSEX ccx;
ccx.dwSize = sizeof(INITCOMMONCONTROLSEX);
ccx.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&ccx);
InitializeClass(&PaintBoxWndProc, "PaintBox");
InitializeClass(&GraphBoxWndProc, "GraphBox");
InitializeClass(&AdvancedBoxWndProc, "AdvancedBox");
HDC dc = GetDC(0);
g_large_fonts = GetDeviceCaps(dc, LOGPIXELSX);
ReleaseDC(0, dc);
g_message_taskbar_created = RegisterWindowMessage(TEXT("TaskbarCreated"));
g_icons[0] = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
g_icons[1] = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON0));
g_ui_window = CreateDialog(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), NULL, &DlgProc);
if (!g_ui_window)
return false;
DragAcceptFiles(g_ui_window, TRUE);
ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD);
ChangeWindowMessageFilter(0x0049, MSGFLT_ADD);
ChangeWindowMessageFilter(WM_USER + 10, MSGFLT_ADD);
TCITEM tabitem;
HWND hwnd_tab = GetDlgItem(g_ui_window, IDC_TAB);
hwndTab = hwnd_tab;
tabitem.mask = TCIF_TEXT;
tabitem.pszText = "Logs";
TabCtrl_InsertItem(hwnd_tab, 0, &tabitem);
tabitem.pszText = "Charts";
TabCtrl_InsertItem(hwnd_tab, 1, &tabitem);
tabitem.pszText = "Advanced";
TabCtrl_InsertItem(hwnd_tab, 2, &tabitem);
SetWindowLong(hwnd_tab, GWL_EXSTYLE, GetWindowLong(hwnd_tab, GWL_EXSTYLE) | WS_EX_COMPOSITED);
hwndEdit = GetDlgItem(g_ui_window, IDC_RICHEDIT21);
hwndPaintBox = GetDlgItem(g_ui_window, IDC_PAINTBOX);
hwndGraphBox = GetDlgItem(g_ui_window, IDC_GRAPHBOX);
hwndAdvancedBox = GetDlgItem(g_ui_window, IDC_ADVANCEDBOX);
SetWindowLong(hwndEdit, GWL_EXSTYLE, GetWindowLong(hwndEdit, GWL_EXSTYLE) &~ WS_EX_CLIENTEDGE);
// Create the status bar.
hwndStatus = CreateWindowEx(
WS_EX_COMPOSITED, STATUSCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, g_ui_window,
(HMENU)IDC_STATUSBAR, g_hinstance, NULL);
HandleWindowSizing();
UpdateTabSelection();
return true;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
g_hinstance = hInstance;
InitCpuFeatures();
WSADATA wsaData = {0};
WSAStartup(MAKEWORD(2, 2), &wsaData);
bool minimize = false;
bool is_autostart = false;
const char *filename = NULL;
for (int i = 1; i < __argc; i++) {
const char *arg = __argv[i];
if (strcmp(arg, "/minimize") == 0) {
minimize = true;
} else if (strcmp(arg, "/minimize_on_connect") == 0) {
g_minimize_on_connect = true;
} else if (strcmp(arg, "/allow_pre_post") == 0) {
g_allow_pre_post = true;
} else if (strcmp(arg, "--service") == 0) {
RunProcessAsTunsafeServiceProcess();
return 0;
} else if (strcmp(arg, "--delete-service-and-start") == 0) {
UninstallTunSafeWindowsService();
} else if (strcmp(arg, "--autostart") == 0) {
is_autostart = true;
} else if (strcmp(arg, "--set-allow-pre-post") == 0) {
bool want = i + 1 < __argc && atoi(__argv[i + 1]) != 0;
RegCreateKeyEx(HKEY_LOCAL_MACHINE, "Software\\TunSafe", NULL, NULL, 0, KEY_ALL_ACCESS, NULL, &g_hklm_reg_key, NULL);
RegWriteInt(g_hklm_reg_key, "AllowPrePost", want);
return 0;
} else if (strcmp(arg, "--import") == 0) {
if (i + 1 >= __argc) return 1;
const char *filename = __argv[i + 1];
return ImportFile(filename, true);
} else {
filename = arg;
break;
}
}
SetProcessDPIAware();
RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\TunSafe", NULL, NULL, 0, KEY_ALL_ACCESS, NULL, &g_reg_key, NULL);
RegCreateKeyEx(HKEY_LOCAL_MACHINE, "Software\\TunSafe", NULL, NULL, 0, KEY_ALL_ACCESS, NULL, &g_hklm_reg_key, NULL);
RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\TunSafe", 0, KEY_READ, &g_hklm_readonly_reg_key);
g_startup_flags = RegReadInt(g_reg_key, "StartupFlags", 0);
if (is_autostart) {
g_disable_connect_on_start = !(g_startup_flags & kStartupFlag_ConnectWhenWindowsStarts);
minimize = !!(g_startup_flags & kStartupFlag_MinimizeToTrayWhenWindowsStarts);
}
// Check if the app is already running.
g_runonce_mutex = CreateMutexA(0, FALSE, "TunSafe-f19e092db01cbe0fb6aee132f8231e5b71c98f90");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
HWND window = FindWindow("TunSafe-f19e092db01cbe0fb6aee132f8231e5b71c98f90", NULL);
DWORD_PTR result;
if (!window || !SendMessageTimeout(window, WM_USER + 10, 0, 0, SMTO_BLOCK, 3000, &result) || result != 31337) {
MessageBoxA(NULL, "It looks like TunSafe is already running, but not responding. Please kill the old process first.", "TunSafe", MB_ICONWARNING);
}
return 1;
}
TOKEN_ELEVATION_TYPE toktype;
g_is_limited_uac_account = (GetProcessElevationType(&toktype) && toktype == TokenElevationTypeLimited);
g_is_tunsafe_service_running = IsTunsafeServiceRunning();
bool want_use_service = !!(g_startup_flags & (kStartupFlag_BackgroundService | kStartupFlag_ForegroundService));
// Re-launch the process as administrator if the TunSafe service isn't running.
if ((!g_is_tunsafe_service_running || !want_use_service) && g_is_limited_uac_account) {
CloseHandle(g_runonce_mutex);
if (!RestartProcessAsAdministrator())
MessageBoxA(0, "TunSafe needs to run as Administrator unless the TunSafe Service is started.", "TunSafe", MB_ICONWARNING);
return 0;
}
CreateNotificationWindow();
g_backend_delegate = CreateTunsafeBackendDelegateThreaded(&my_procdel, []() {
if (g_ui_window)
PostMessage(g_ui_window, WM_USER + 2, 0, 0);
});
g_logger = &PushLine;
if (!CreateMainWindow())
return 1;
g_current_filename = _strdup("");
g_cmdline_filename = filename;
if (!g_allow_pre_post && g_hklm_readonly_reg_key)
g_allow_pre_post = RegReadInt(g_hklm_readonly_reg_key, "AllowPrePost", 0) != 0;
// Attempt to start service...
if (want_use_service && !g_is_tunsafe_service_running) {
RINFO("Starting TunSafe service...");
InstallTunSafeWindowsService();
}
CreateLocalOrRemoteBackend(want_use_service);
if (!minimize) {
SetUiVisibility(true);
}
UpdateIcon(UIW_NONE);
EnsureConfigDirCreated();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (!IsDialogMessage(g_ui_window, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
if (!g_backend->is_remote())
g_backend->Stop();
delete g_backend;
RemoveIcon();
return 0;
}