// SPDX-License-Identifier: AGPL-1.0-only // Copyright (C) 2018 Ludvig Strigeus . All Rights Reserved. #include "stdafx.h" #include "wireguard_config.h" #include "network_win32_api.h" #include "network_win32_dnsblock.h" #include #include #include #include #include #include "resource.h" #include #include #include #include #include #include #include #include #include "tunsafe_endian.h" #include "util.h" #include #include #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 © 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; }