mirror of https://github.com/OpenVPN/openvpn-gui
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1163 lines
34 KiB
1163 lines
34 KiB
/* |
|
* OpenVPN-GUI -- A Windows GUI for OpenVPN. |
|
* |
|
* Copyright (C) 2004 Mathias Sundman <mathias@nilings.se> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program (see the file COPYING included with this |
|
* distribution); if not, write to the Free Software Foundation, Inc., |
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include <config.h> |
|
#endif |
|
|
|
#if !defined (UNICODE) |
|
#error UNICODE and _UNICODE must be defined. This version only supports unicode builds. |
|
#endif |
|
|
|
#include <windows.h> |
|
#include <versionhelpers.h> |
|
#include <shlwapi.h> |
|
#include <wtsapi32.h> |
|
#include <prsht.h> |
|
#include <commdlg.h> |
|
|
|
#include "tray.h" |
|
#include "openvpn.h" |
|
#include "openvpn_config.h" |
|
#include "viewlog.h" |
|
#include "service.h" |
|
#include "main.h" |
|
#include "options.h" |
|
#include "proxy.h" |
|
#include "registry.h" |
|
#include "openvpn-gui-res.h" |
|
#include "localization.h" |
|
#include "manage.h" |
|
#include "misc.h" |
|
#include "save_pass.h" |
|
#include "echo.h" |
|
#include "as.h" |
|
|
|
#define OVPN_EXITCODE_ERROR 1 |
|
#define OVPN_EXITCODE_TIMEOUT 2 |
|
#define OVPN_EXITCODE_NOTREADY 3 |
|
|
|
/* Declare Windows procedure */ |
|
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM); |
|
|
|
static void ShowSettingsDialog(); |
|
|
|
void CloseApplication(HWND hwnd, BOOL ask_user); |
|
|
|
void ImportConfigFileFromDisk(); |
|
|
|
void ImportConfigFromAS(); |
|
|
|
static void SaveAutoRestartList(); |
|
|
|
static void LoadAutoRestartList(); |
|
|
|
/* Class name and window title */ |
|
TCHAR szClassName[] = _T("OpenVPN-GUI"); |
|
TCHAR szTitleText[] = _T("OpenVPN"); |
|
|
|
/* Options structure */ |
|
options_t o; |
|
|
|
/* Workaround for ASLR on Windows */ |
|
__declspec(dllexport) char aslr_workaround; |
|
|
|
/* globals */ |
|
static HWND settings_window; /* Handle of Settings window */ |
|
|
|
static int |
|
VerifyAutoConnections() |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < o.num_auto_connect; i++) |
|
{ |
|
if (GetConnByName(o.auto_connect[i]) == NULL) |
|
{ |
|
/* autostart config not found */ |
|
ShowLocalizedMsg(IDS_ERR_AUTOSTART_CONF, o.auto_connect[i]); |
|
return FALSE; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
/* |
|
* Send a copydata message corresponding to any --command action option specified |
|
* to the running instance and return success or error. |
|
*/ |
|
static int |
|
NotifyRunningInstance() |
|
{ |
|
/* Check if a previous instance has a window initialized |
|
* Even if we are not the first instance this may return null |
|
* if the previous instance has not fully started up |
|
*/ |
|
HANDLE hwnd_master = FindWindow(szClassName, NULL); |
|
int exit_code = 0; |
|
if (hwnd_master) |
|
{ |
|
/* GUI up and running -- send a message if any action is pecified, |
|
* else show the balloon */ |
|
COPYDATASTRUCT config_data = {0}; |
|
int timeout = 30*1000; /* 30 seconds */ |
|
if (!o.action) |
|
{ |
|
o.action = WM_OVPN_NOTIFY; |
|
o.action_arg = LoadLocalizedString(IDS_NFO_CLICK_HERE_TO_START); |
|
} |
|
config_data.dwData = o.action; |
|
if (o.action_arg) |
|
{ |
|
config_data.cbData = (wcslen(o.action_arg)+1)*sizeof(o.action_arg[0]); |
|
config_data.lpData = (void *) o.action_arg; |
|
} |
|
PrintDebug(L"Instance 2: called with action %d : %ls", o.action, o.action_arg); |
|
if (!SendMessageTimeout(hwnd_master, WM_COPYDATA, 0, |
|
(LPARAM) &config_data, 0, timeout, NULL)) |
|
{ |
|
DWORD error = GetLastError(); |
|
if (error == ERROR_TIMEOUT) |
|
{ |
|
exit_code = OVPN_EXITCODE_TIMEOUT; |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Sending command to running instance timed out."); |
|
} |
|
else |
|
{ |
|
exit_code = OVPN_EXITCODE_ERROR; |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Sending command to running instance failed (error = %lu).", |
|
error); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
/* An instance is already running but its main window not yet initialized */ |
|
exit_code = OVPN_EXITCODE_NOTREADY; |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Previous instance not yet ready to accept commands. " |
|
"Try again later."); |
|
} |
|
|
|
return exit_code; |
|
} |
|
|
|
int WINAPI |
|
_tWinMain(HINSTANCE hThisInstance, |
|
UNUSED HINSTANCE hPrevInstance, |
|
UNUSED LPTSTR lpszArgument, |
|
UNUSED int nCmdShow) |
|
{ |
|
MSG messages; /* Here messages to the application are saved */ |
|
WNDCLASSEX wincl; /* Data structure for the windowclass */ |
|
DWORD shell32_version; |
|
BOOL first_instance = TRUE; |
|
/* a session local semaphore to detect second instance */ |
|
HANDLE session_semaphore = InitSemaphore(L"Local\\"PACKAGE_NAME); |
|
|
|
srand(time(NULL)); |
|
/* try to lock the semaphore, else we are not the first instance */ |
|
if (session_semaphore |
|
&& WaitForSingleObject(session_semaphore, 200) != WAIT_OBJECT_0) |
|
{ |
|
first_instance = FALSE; |
|
} |
|
|
|
/* Initialize handlers for manangement interface notifications */ |
|
mgmt_rtmsg_handler handler[] = { |
|
{ ready_, OnReady }, |
|
{ hold_, OnHold }, |
|
{ log_, OnLogLine }, |
|
{ state_, OnStateChange }, |
|
{ password_, OnPassword }, |
|
{ proxy_, OnProxy }, |
|
{ stop_, OnStop }, |
|
{ needok_, OnNeedOk }, |
|
{ needstr_, OnNeedStr }, |
|
{ echo_, OnEcho }, |
|
{ bytecount_, OnByteCount }, |
|
{ infomsg_, OnInfoMsg }, |
|
{ timeout_, OnTimeout }, |
|
{ 0, NULL } |
|
}; |
|
InitManagement(handler); |
|
|
|
/* initialize options to default state */ |
|
InitOptions(&o); |
|
if (first_instance) |
|
{ |
|
o.session_semaphore = session_semaphore; |
|
} |
|
|
|
#ifdef DEBUG |
|
/* Open debug file for output */ |
|
if (!(o.debug_fp = _wfopen(DEBUG_FILE, L"a+,ccs=UTF-8"))) |
|
{ |
|
/* can't open debug file */ |
|
ShowLocalizedMsg(IDS_ERR_OPEN_DEBUG_FILE, DEBUG_FILE); |
|
exit(1); |
|
} |
|
PrintDebug(_T("Starting OpenVPN GUI v%hs"), PACKAGE_VERSION); |
|
#endif |
|
|
|
|
|
o.hInstance = hThisInstance; |
|
|
|
if (!GetModuleHandle(_T("RICHED20.DLL"))) |
|
{ |
|
LoadLibrary(_T("RICHED20.DLL")); |
|
} |
|
else |
|
{ |
|
/* can't load riched20.dll */ |
|
ShowLocalizedMsg(IDS_ERR_LOAD_RICHED20); |
|
exit(1); |
|
} |
|
|
|
/* Check version of shell32.dll */ |
|
shell32_version = GetDllVersion(_T("shell32.dll")); |
|
if (shell32_version < PACKVERSION(5, 0)) |
|
{ |
|
/* shell32.dll version to low */ |
|
ShowLocalizedMsg(IDS_ERR_SHELL_DLL_VERSION, shell32_version); |
|
exit(1); |
|
} |
|
#ifdef DEBUG |
|
PrintDebug(_T("Shell32.dll version: 0x%lx"), shell32_version); |
|
#endif |
|
|
|
if (first_instance) |
|
{ |
|
UpdateRegistry(); /* Checks version change and update keys/values */ |
|
|
|
} |
|
GetRegistryKeys(); |
|
/* Parse command-line options */ |
|
ProcessCommandLine(&o, GetCommandLine()); |
|
|
|
EnsureDirExists(o.config_dir); |
|
|
|
if (!first_instance) |
|
{ |
|
int res = NotifyRunningInstance(); |
|
exit(res); |
|
} |
|
else if (o.action == WM_OVPN_START) |
|
{ |
|
PrintDebug(L"Instance 1: Called with --command connect xxx. Treating it as --connect xxx"); |
|
} |
|
else if (o.action == WM_OVPN_IMPORT) |
|
{ |
|
/* pass -- import is handled after Window initialization */ |
|
} |
|
else if (o.action) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Called with --command when no previous instance available"); |
|
exit(OVPN_EXITCODE_ERROR); |
|
} |
|
|
|
if (!CheckVersion()) |
|
{ |
|
exit(1); |
|
} |
|
|
|
if (!EnsureDirExists(o.log_dir)) |
|
{ |
|
ShowLocalizedMsg(IDS_ERR_CREATE_PATH, _T("log_dir"), o.log_dir); |
|
exit(1); |
|
} |
|
|
|
BOOL use_iservice = (o.iservice_admin && IsWindows7OrGreater()) || !IsUserAdmin(); |
|
if (use_iservice && strtod(o.ovpn_version, NULL) > 2.3 && !o.silent_connection) |
|
{ |
|
CheckIServiceStatus(TRUE); |
|
} |
|
|
|
CheckServiceStatus(); /* Check if automatic service is running or not */ |
|
BuildFileList(); |
|
|
|
if (!VerifyAutoConnections()) |
|
{ |
|
exit(1); |
|
} |
|
|
|
GetProxyRegistrySettings(); |
|
|
|
/* The Window structure */ |
|
wincl.hInstance = hThisInstance; |
|
wincl.lpszClassName = szClassName; |
|
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */ |
|
wincl.style = CS_DBLCLKS; /* Catch double-clicks */ |
|
wincl.cbSize = sizeof(WNDCLASSEX); |
|
|
|
/* Use default icon and mouse-pointer */ |
|
wincl.hIcon = LoadLocalizedIcon(ID_ICO_APP); |
|
wincl.hIconSm = LoadLocalizedIcon(ID_ICO_APP); |
|
wincl.hCursor = LoadCursor(NULL, IDC_ARROW); |
|
wincl.lpszMenuName = NULL; /* No menu */ |
|
wincl.cbClsExtra = 0; /* No extra bytes after the window class */ |
|
wincl.cbWndExtra = 0; /* structure or the window instance */ |
|
/* Use Windows's default color as the background of the window */ |
|
wincl.hbrBackground = (HBRUSH) COLOR_3DSHADOW; /*COLOR_BACKGROUND; */ |
|
|
|
/* Register the window class, and if it fails quit the program */ |
|
if (!RegisterClassEx(&wincl)) |
|
{ |
|
return 1; |
|
} |
|
|
|
/* The class is registered, let's create the program*/ |
|
CreateWindowEx( |
|
0, /* Extended possibilites for variation */ |
|
szClassName, /* Classname */ |
|
szTitleText, /* Title Text */ |
|
WS_OVERLAPPEDWINDOW, /* default window */ |
|
(int)CW_USEDEFAULT, /* Windows decides the position */ |
|
(int)CW_USEDEFAULT, /* where the window ends up on the screen */ |
|
230, /* The programs width */ |
|
200, /* and height in pixels */ |
|
HWND_DESKTOP, /* The window is a child-window to desktop */ |
|
NULL, /* No menu */ |
|
hThisInstance, /* Program Instance handler */ |
|
NULL /* No Window Creation data */ |
|
); |
|
|
|
|
|
/* Run the message loop. It will run until GetMessage() returns 0 */ |
|
while (GetMessage(&messages, NULL, 0, 0)) |
|
{ |
|
TranslateMessage(&messages); |
|
DispatchMessage(&messages); |
|
} |
|
|
|
CloseSemaphore(o.session_semaphore); |
|
o.session_semaphore = NULL; /* though we're going to die.. */ |
|
if (o.event_log) |
|
{ |
|
DeregisterEventSource(o.event_log); |
|
} |
|
|
|
/* The program return-value is 0 - The value that PostQuitMessage() gave */ |
|
return messages.wParam; |
|
} |
|
|
|
|
|
static void |
|
StopAllOpenVPN(bool exiting) |
|
{ |
|
int i; |
|
|
|
if (exiting) |
|
{ |
|
RemoveTrayIcon(); |
|
} |
|
|
|
/* Stop all connections started by us -- we leave persistent ones |
|
* at their current state. Use the disconnect menu to put them into |
|
* hold state before exit, if desired. |
|
*/ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->state != disconnected && c->state != detached) |
|
{ |
|
if (c->flags & FLAG_DAEMON_PERSISTENT) |
|
{ |
|
DetachOpenVPN(c); |
|
} |
|
else |
|
{ |
|
StopOpenVPN(c); |
|
} |
|
} |
|
} |
|
|
|
/* Wait for all connections to terminate (Max 20 rounds of 250 msec = 5 sec) */ |
|
for (i = 0; i < 20; i++) |
|
{ |
|
if (CountConnState(disconnected) + CountConnState(detached) == o.num_configs |
|
|| !OVPNMsgWait(250, NULL)) /* Quit received */ |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
static int |
|
AutoStartConnections() |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->auto_connect && !(c->flags & FLAG_DAEMON_PERSISTENT)) |
|
{ |
|
StartOpenVPN(c); |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
|
|
static void |
|
ResumeConnections() |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
/* Restart suspend connections */ |
|
if (c->state == suspended) |
|
{ |
|
StartOpenVPN(c); |
|
} |
|
|
|
/* If some connection never reached SUSPENDED state */ |
|
if (c->state == suspending) |
|
{ |
|
StopOpenVPN(c); |
|
} |
|
} |
|
} |
|
|
|
static int |
|
HandleCopyDataMessage(const COPYDATASTRUCT *copy_data) |
|
{ |
|
WCHAR *str = NULL; |
|
connection_t *c = NULL; |
|
PrintDebug(L"WM_COPYDATA message received. (dwData: %lu, cbData: %lu, lpData: %ls)", |
|
copy_data->dwData, copy_data->cbData, copy_data->lpData); |
|
if (copy_data->cbData >= sizeof(WCHAR) && copy_data->lpData) |
|
{ |
|
str = (WCHAR *) copy_data->lpData; |
|
str[copy_data->cbData/sizeof(WCHAR)-1] = L'\0'; /* in case not nul-terminated */ |
|
c = GetConnByName(str); |
|
} |
|
if (copy_data->dwData == WM_OVPN_START && c) |
|
{ |
|
if (!o.silent_connection) |
|
{ |
|
ForceForegroundWindow(o.hWnd); |
|
} |
|
StartOpenVPN(c); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_STOP && c) |
|
{ |
|
StopOpenVPN(c); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_RESTART && c) |
|
{ |
|
if (!o.silent_connection) |
|
{ |
|
ForceForegroundWindow(o.hWnd); |
|
} |
|
RestartOpenVPN(c); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_SHOWSTATUS && c && c->hwndStatus) |
|
{ |
|
ForceForegroundWindow(o.hWnd); |
|
ShowWindow(c->hwndStatus, SW_SHOW); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_STOPALL) |
|
{ |
|
StopAllOpenVPN(false); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_SILENT && str) |
|
{ |
|
if (_wtoi(str) == 0) |
|
{ |
|
o.silent_connection = 0; |
|
} |
|
else |
|
{ |
|
o.silent_connection = 1; |
|
} |
|
} |
|
else if (copy_data->dwData == WM_OVPN_EXIT) |
|
{ |
|
CloseApplication(o.hWnd, true); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_IMPORT && str) |
|
{ |
|
ImportConfigFile(str, true); /* prompt user */ |
|
} |
|
else if (copy_data->dwData == WM_OVPN_NOTIFY) |
|
{ |
|
ShowTrayBalloon(L"", copy_data->lpData); |
|
} |
|
else if (copy_data->dwData == WM_OVPN_RESCAN) |
|
{ |
|
OnNotifyTray(WM_OVPN_RESCAN); |
|
} |
|
else |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, |
|
L"Unknown WM_COPYDATA message ignored. (dwData: %lu, cbData: %lu, lpData: %ls)", |
|
copy_data->dwData, copy_data->cbData, copy_data->lpData); |
|
return FALSE; |
|
} |
|
return TRUE; /* indicate we handled the message */ |
|
} |
|
|
|
/* If automatic service is running, check whether we are |
|
* attached to the management i/f of persistent daemons |
|
* and re-attach if necessary. The timer is reset to |
|
* call this routine again after a delay. Periodic check |
|
* is required as we get detached if the daemon gets restarted |
|
* by the service or we do a disconnect. |
|
*/ |
|
static void CALLBACK |
|
ManagePersistent(HWND hwnd, UINT UNUSED msg, UINT_PTR id, DWORD UNUSED now) |
|
{ |
|
CheckServiceStatus(); |
|
if (o.service_state == service_connected) |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->flags & FLAG_DAEMON_PERSISTENT |
|
&& c->auto_connect |
|
&& (c->state == disconnected || c->state == detached)) |
|
{ |
|
/* disable auto-connect to avoid repeated re-connect |
|
* after unrecoverable errors. Re-enabled on successful |
|
* connect. |
|
*/ |
|
c->auto_connect = false; |
|
c->state = detached; /* this is required to retain management-hold on re-attach */ |
|
StartOpenVPN(c); /* attach to the management i/f */ |
|
} |
|
} |
|
} |
|
/* schedule to call again after 10 sec */ |
|
SetTimer(hwnd, id, 10000, ManagePersistent); |
|
} |
|
|
|
/* Detach from the mgmt i/f of all atatched persistent |
|
* connections. Called on session lock so that another |
|
* user can attach to these. |
|
*/ |
|
static void |
|
HandleSessionLock(void) |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->flags & FLAG_DAEMON_PERSISTENT |
|
&& (c->state != disconnected && c->state != detached)) |
|
{ |
|
c->auto_connect = false; |
|
DetachOpenVPN(c); |
|
c->flags |= FLAG_WAIT_UNLOCK; |
|
} |
|
} |
|
} |
|
|
|
/* Undo any actions done at session lock/disconnect. |
|
*/ |
|
void |
|
HandleSessionUnlock(void) |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->flags & FLAG_WAIT_UNLOCK) |
|
{ |
|
c->auto_connect = true; /* so that ManagePersistent will trigger attach */ |
|
c->flags &= ~FLAG_WAIT_UNLOCK; |
|
} |
|
} |
|
} |
|
|
|
/* This function is called by the Windows function DispatchMessage() */ |
|
LRESULT CALLBACK |
|
WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
|
{ |
|
static UINT s_uTaskbarRestart; |
|
MENUINFO minfo = {.cbSize = sizeof(MENUINFO)}; |
|
connection_t *c = NULL; |
|
|
|
switch (message) |
|
{ |
|
case WM_CREATE: |
|
|
|
/* Save Window Handle */ |
|
o.hWnd = hwnd; |
|
dpi_initialize(&o); |
|
|
|
s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); |
|
|
|
WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION); |
|
|
|
/* Load application icon */ |
|
HICON hIcon = LoadLocalizedIcon(ID_ICO_APP); |
|
if (hIcon) |
|
{ |
|
SendMessage(hwnd, WM_SETICON, (WPARAM) (ICON_SMALL), (LPARAM) (hIcon)); |
|
SendMessage(hwnd, WM_SETICON, (WPARAM) (ICON_BIG), (LPARAM) (hIcon)); |
|
} |
|
|
|
/* Enable next line to accept WM_COPYDATA messages from lower level processes */ |
|
#if 0 |
|
ChangeWindowMessageFilterEx(hwnd, WM_COPYDATA, MSGFLT_ALLOW, NULL); |
|
#endif |
|
|
|
echo_msg_init(); |
|
|
|
CreatePopupMenus(); /* Create popup menus */ |
|
ShowTrayIcon(); |
|
|
|
/* if '--import' was specified, do it now */ |
|
if (o.action == WM_OVPN_IMPORT && o.action_arg) |
|
{ |
|
ImportConfigFile(o.action_arg, true); /* prompt user */ |
|
} |
|
|
|
if (o.enable_auto_restart) |
|
{ |
|
LoadAutoRestartList(); |
|
} |
|
|
|
if (!AutoStartConnections()) |
|
{ |
|
SendMessage(hwnd, WM_CLOSE, 0, 0); |
|
break; |
|
} |
|
/* A timer to periodically tend to persistent connections */ |
|
SetTimer(hwnd, 1, 100, ManagePersistent); |
|
|
|
break; |
|
|
|
case WM_NOTIFYICONTRAY: |
|
OnNotifyTray(lParam); /* Manages message from tray */ |
|
break; |
|
|
|
case WM_COPYDATA: /* custom messages with data from other processes */ |
|
HandleCopyDataMessage((COPYDATASTRUCT *) lParam); |
|
return TRUE; /* lets the sender free copy_data */ |
|
|
|
case WM_MENUCOMMAND: |
|
/* Get the menu item id and save it in wParam for use below */ |
|
wParam = GetMenuItemID((HMENU) lParam, wParam); |
|
|
|
/* we first check global menu items which do not require a connnection index */ |
|
if (LOWORD(wParam) == IDM_IMPORT_FILE) |
|
{ |
|
ImportConfigFileFromDisk(); |
|
} |
|
else if (LOWORD(wParam) == IDM_IMPORT_AS) |
|
{ |
|
ImportConfigFromAS(); |
|
} |
|
else if (LOWORD(wParam) == IDM_IMPORT_URL) |
|
{ |
|
ImportConfigFromURL(); |
|
} |
|
else if (LOWORD(wParam) == IDM_SETTINGS) |
|
{ |
|
ShowSettingsDialog(); |
|
} |
|
else if (LOWORD(wParam) == IDM_CLOSE) |
|
{ |
|
CloseApplication(hwnd, true); |
|
} |
|
/* rest of the handlers require a connection id */ |
|
else |
|
{ |
|
minfo.fMask = MIM_MENUDATA; |
|
GetMenuInfo((HMENU) lParam, &minfo); |
|
c = (connection_t *) minfo.dwMenuData; |
|
if (!c) |
|
{ |
|
break; /* ignore invalid connection */ |
|
} |
|
} |
|
|
|
/* reach here only if the command did not match any global items and a valid connection id is available */ |
|
|
|
if (LOWORD(wParam) == IDM_CONNECTMENU) |
|
{ |
|
StartOpenVPN(c); |
|
} |
|
else if (LOWORD(wParam) == IDM_DISCONNECTMENU) |
|
{ |
|
StopOpenVPN(c); |
|
} |
|
else if (LOWORD(wParam) == IDM_RECONNECTMENU) |
|
{ |
|
RestartOpenVPN(c); |
|
} |
|
else if (LOWORD(wParam) == IDM_STATUSMENU) |
|
{ |
|
ShowWindow(c->hwndStatus, SW_SHOW); |
|
} |
|
else if (LOWORD(wParam) == IDM_VIEWLOGMENU) |
|
{ |
|
ViewLog(c); |
|
} |
|
else if (LOWORD(wParam) == IDM_EDITMENU) |
|
{ |
|
EditConfig(c); |
|
} |
|
else if (LOWORD(wParam) == IDM_CLEARPASSMENU) |
|
{ |
|
ResetSavePasswords(c); |
|
} |
|
break; |
|
|
|
case WM_CLOSE: |
|
CloseApplication(hwnd, false); /* do not wait for user confirmation */ |
|
break; |
|
|
|
case WM_DESTROY: |
|
WTSUnRegisterSessionNotification(hwnd); |
|
StopAllOpenVPN(true); |
|
OnDestroyTray(); /* Remove Tray Icon and destroy menus */ |
|
PostQuitMessage(0); /* Send a WM_QUIT to the message queue */ |
|
break; |
|
|
|
case WM_QUERYENDSESSION: |
|
return(TRUE); |
|
|
|
case WM_ENDSESSION: |
|
SaveAutoRestartList(); |
|
StopAllOpenVPN(true); |
|
OnDestroyTray(); |
|
break; |
|
|
|
case WM_WTSSESSION_CHANGE: |
|
switch (wParam) |
|
{ |
|
case WTS_SESSION_LOCK: |
|
PrintDebug(L"Session lock triggered"); |
|
o.session_locked = TRUE; |
|
/* Detach persistent connections so that other users can connect to it */ |
|
HandleSessionLock(); |
|
KillTimer(hwnd, 1); /* This ensure ManagePersistent is not called when session is locked */ |
|
break; |
|
|
|
case WTS_SESSION_UNLOCK: |
|
PrintDebug(L"Session unlock triggered"); |
|
o.session_locked = FALSE; |
|
HandleSessionUnlock(); |
|
SetTimer(hwnd, 1, 100, ManagePersistent); |
|
if (CountConnState(suspended) != 0) |
|
{ |
|
ResumeConnections(); |
|
} |
|
break; |
|
|
|
default: |
|
PrintDebug(L"Session change with wParam = %lu", wParam); |
|
break; |
|
} |
|
break; |
|
|
|
default: /* for messages that we don't deal with */ |
|
if (message == s_uTaskbarRestart) |
|
{ |
|
/* Explorer has restarted, re-register the tray icon. */ |
|
ShowTrayIcon(); |
|
CheckAndSetTrayIcon(); |
|
break; |
|
} |
|
return DefWindowProc(hwnd, message, wParam, lParam); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static INT_PTR CALLBACK |
|
AboutDialogFunc(UNUSED HWND hDlg, UINT msg, UNUSED WPARAM wParam, LPARAM lParam) |
|
{ |
|
LPPSHNOTIFY psn; |
|
wchar_t tmp1[300], tmp2[300]; |
|
switch (msg) |
|
{ |
|
case WM_INITDIALOG: |
|
if (GetDlgItemText(hDlg, ID_TXT_VERSION, tmp1, _countof(tmp1))) |
|
{ |
|
_sntprintf_0(tmp2, tmp1, TEXT(PACKAGE_VERSION_RESOURCE_STR)); |
|
SetDlgItemText(hDlg, ID_TXT_VERSION, tmp2); |
|
} |
|
/* Modify the ABOUT3 line that reads as "OpenVPN ... " by |
|
* including the version, like "OpenVPN v2.5.8 ... ". |
|
* The logic used depends on this text starting with |
|
* "OpenVPN", which is the case in all languages. |
|
*/ |
|
const wchar_t *prefix = L"OpenVPN "; |
|
if (GetDlgItemText(hDlg, ID_LTEXT_ABOUT3, tmp1, _countof(tmp1)) |
|
&& wcsbegins(tmp1, prefix)) |
|
{ |
|
_sntprintf_0(tmp2, L"%lsv%hs %ls", prefix, o.ovpn_version, tmp1 + wcslen(prefix)); |
|
SetDlgItemText(hDlg, ID_LTEXT_ABOUT3, tmp2); |
|
} |
|
break; |
|
|
|
case WM_NOTIFY: |
|
psn = (LPPSHNOTIFY) lParam; |
|
if (psn->hdr.code == (UINT) PSN_APPLY) |
|
{ |
|
return TRUE; |
|
} |
|
} |
|
return FALSE; |
|
} |
|
|
|
static int CALLBACK |
|
SettingsPsCallback(HWND hwnd, UINT msg, UNUSED LPARAM lParam) |
|
{ |
|
switch (msg) |
|
{ |
|
case PSCB_INITIALIZED: |
|
settings_window = hwnd; |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void |
|
ShowSettingsDialog() |
|
{ |
|
PROPSHEETPAGE psp[4]; |
|
int page_number = 0; |
|
|
|
if (settings_window && IsWindow(settings_window)) |
|
{ |
|
SetForegroundWindow(settings_window); |
|
ShowWindow(settings_window, SW_SHOW); |
|
return; |
|
} |
|
|
|
/* General tab */ |
|
psp[page_number].dwSize = sizeof(PROPSHEETPAGE); |
|
psp[page_number].dwFlags = PSP_DLGINDIRECT; |
|
psp[page_number].hInstance = o.hInstance; |
|
psp[page_number].pResource = LocalizedDialogResource(ID_DLG_GENERAL); |
|
psp[page_number].pfnDlgProc = GeneralSettingsDlgProc; |
|
psp[page_number].lParam = 0; |
|
psp[page_number].pfnCallback = NULL; |
|
++page_number; |
|
|
|
/* Proxy tab */ |
|
psp[page_number].dwSize = sizeof(PROPSHEETPAGE); |
|
psp[page_number].dwFlags = PSP_DLGINDIRECT; |
|
psp[page_number].hInstance = o.hInstance; |
|
psp[page_number].pResource = LocalizedDialogResource(ID_DLG_PROXY); |
|
psp[page_number].pfnDlgProc = ProxySettingsDialogFunc; |
|
psp[page_number].lParam = 0; |
|
psp[page_number].pfnCallback = NULL; |
|
++page_number; |
|
|
|
/* Advanced tab */ |
|
psp[page_number].dwSize = sizeof(PROPSHEETPAGE); |
|
psp[page_number].dwFlags = PSP_DLGINDIRECT; |
|
psp[page_number].hInstance = o.hInstance; |
|
psp[page_number].pResource = LocalizedDialogResource(ID_DLG_ADVANCED); |
|
psp[page_number].pfnDlgProc = AdvancedSettingsDlgProc; |
|
psp[page_number].lParam = 0; |
|
psp[page_number].pfnCallback = NULL; |
|
++page_number; |
|
|
|
/* About tab */ |
|
psp[page_number].dwSize = sizeof(PROPSHEETPAGE); |
|
psp[page_number].dwFlags = PSP_DLGINDIRECT; |
|
psp[page_number].hInstance = o.hInstance; |
|
psp[page_number].pResource = LocalizedDialogResource(ID_DLG_ABOUT); |
|
psp[page_number].pfnDlgProc = AboutDialogFunc; |
|
psp[page_number].lParam = 0; |
|
psp[page_number].pfnCallback = NULL; |
|
++page_number; |
|
|
|
PROPSHEETHEADER psh; |
|
psh.dwSize = sizeof(PROPSHEETHEADER); |
|
psh.dwFlags = PSH_USEHICON | PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW | PSH_NOCONTEXTHELP | PSH_USECALLBACK; |
|
psh.hwndParent = o.hWnd; |
|
psh.hInstance = o.hInstance; |
|
psh.hIcon = LoadLocalizedIcon(ID_ICO_APP); |
|
psh.pszCaption = LoadLocalizedString(IDS_SETTINGS_CAPTION); |
|
psh.nPages = page_number; |
|
psh.nStartPage = 0; |
|
psh.ppsp = (LPCPROPSHEETPAGE) &psp; |
|
psh.pfnCallback = SettingsPsCallback; |
|
|
|
PropertySheet(&psh); |
|
} |
|
|
|
|
|
void |
|
CloseApplication(HWND hwnd, BOOL ask_user) |
|
{ |
|
/* Do not let user access main menu through tray icon */ |
|
RemoveTrayIcon(); |
|
/* Show a message if any non-persistent connections are active */ |
|
for (connection_t *c = o.chead; c && ask_user; c = c->next) |
|
{ |
|
if (c->state == disconnected |
|
|| c->flags & FLAG_DAEMON_PERSISTENT) |
|
{ |
|
continue; |
|
} |
|
|
|
/* Ask for confirmation if still connected */ |
|
if (ShowLocalizedMsgEx(MB_YESNO|MB_TOPMOST, o.hWnd, _T("Exit OpenVPN"), IDS_NFO_ACTIVE_CONN_EXIT) == IDNO) |
|
{ |
|
/* recreate the tray icon */ |
|
ShowTrayIcon(); |
|
CheckAndSetTrayIcon(); |
|
return; |
|
} |
|
break; /* show the above message box only once */ |
|
} |
|
|
|
SaveAutoRestartList(); /* active connection names saved in registry */ |
|
|
|
DestroyWindow(hwnd); |
|
} |
|
|
|
void |
|
ImportConfigFileFromDisk() |
|
{ |
|
TCHAR filter[2*_countof(o.ext_string)+5]; |
|
|
|
_sntprintf_0(filter, _T("*.%ls%lc*.%ls%lc"), o.ext_string, _T('\0'), o.ext_string, _T('\0')); |
|
|
|
OPENFILENAME fn; |
|
TCHAR source[MAX_PATH] = _T(""); |
|
|
|
fn.lStructSize = sizeof(OPENFILENAME); |
|
fn.hwndOwner = NULL; |
|
fn.lpstrFilter = filter; |
|
fn.lpstrCustomFilter = NULL; |
|
fn.nFilterIndex = 1; |
|
fn.lpstrFile = source; |
|
fn.nMaxFile = MAX_PATH; |
|
fn.lpstrFileTitle = NULL; |
|
fn.lpstrInitialDir = NULL; |
|
fn.lpstrTitle = NULL; |
|
fn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST; |
|
fn.lpstrDefExt = NULL; |
|
|
|
if (GetOpenFileName(&fn)) |
|
{ |
|
ImportConfigFile(source, false); /* do not prompt user */ |
|
} |
|
} |
|
|
|
#ifdef DEBUG |
|
void |
|
PrintDebugMsg(TCHAR *msg) |
|
{ |
|
time_t log_time; |
|
struct tm *time_struct; |
|
TCHAR date[30]; |
|
|
|
log_time = time(NULL); |
|
time_struct = localtime(&log_time); |
|
_sntprintf(date, _countof(date), _T("%d-%.2d-%.2d %.2d:%.2d:%.2d"), |
|
time_struct->tm_year + 1900, |
|
time_struct->tm_mon + 1, |
|
time_struct->tm_mday, |
|
time_struct->tm_hour, |
|
time_struct->tm_min, |
|
time_struct->tm_sec); |
|
|
|
_ftprintf(o.debug_fp, _T("%ls %ls\n"), date, msg); |
|
fflush(o.debug_fp); |
|
} |
|
#endif /* ifdef DEBUG */ |
|
|
|
#define PACKVERSION(major, minor) MAKELONG(minor, major) |
|
DWORD |
|
GetDllVersion(LPCTSTR lpszDllName) |
|
{ |
|
HINSTANCE hinstDll; |
|
DWORD dwVersion = 0; |
|
|
|
/* For security purposes, LoadLibrary should be provided with a |
|
* fully-qualified path to the DLL. The lpszDllName variable should be |
|
* tested to ensure that it is a fully qualified path before it is used. */ |
|
hinstDll = LoadLibrary(lpszDllName); |
|
|
|
if (hinstDll) |
|
{ |
|
DLLGETVERSIONPROC pDllGetVersion; |
|
pDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hinstDll, |
|
"DllGetVersion"); |
|
|
|
/* Because some DLLs might not implement this function, you |
|
* must test for it explicitly. Depending on the particular |
|
* DLL, the lack of a DllGetVersion function can be a useful |
|
* indicator of the version. */ |
|
|
|
if (pDllGetVersion) |
|
{ |
|
DLLVERSIONINFO dvi; |
|
HRESULT hr; |
|
|
|
ZeroMemory(&dvi, sizeof(dvi)); |
|
dvi.cbSize = sizeof(dvi); |
|
|
|
hr = (*pDllGetVersion)(&dvi); |
|
|
|
if (SUCCEEDED(hr)) |
|
{ |
|
dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion); |
|
} |
|
} |
|
|
|
FreeLibrary(hinstDll); |
|
} |
|
return dwVersion; |
|
} |
|
|
|
void |
|
ErrorExit(int exit_code, const wchar_t *msg) |
|
{ |
|
if (msg) |
|
{ |
|
MessageBoxExW(NULL, msg, TEXT(PACKAGE_NAME), |
|
MB_OK | MB_SETFOREGROUND | MB_ICONERROR | MBOX_RTL_FLAGS, GetGUILanguage()); |
|
} |
|
if (o.hWnd) |
|
{ |
|
StopAllOpenVPN(true); |
|
PostQuitMessage(exit_code); |
|
} |
|
else |
|
{ |
|
exit(exit_code); |
|
} |
|
} |
|
|
|
/** |
|
* Save a list of active connections in registry. If enable_auto_restart |
|
* is false, any previously saved list is deleted and no new list is saved. |
|
* Do not show any messages here as this may be called on receiving |
|
* WM_ENDSESSION (user logout). |
|
*/ |
|
static void |
|
SaveAutoRestartList() |
|
{ |
|
size_t len = 0; |
|
int nactive = 0; |
|
int max_active = o.num_configs - CountConnState(disconnected); |
|
|
|
if (!o.enable_auto_restart || max_active <= 0) |
|
{ |
|
/* delete the list -- on load this gets treated as an empty list */ |
|
RegDeleteKeyValueW(HKEY_CURRENT_USER, GUI_REGKEY_HKCU, L"auto_restart_list"); |
|
return; |
|
} |
|
|
|
connection_t **active_conns = malloc((size_t) max_active*sizeof(connection_t *)); |
|
|
|
if (!active_conns) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Out of memory while persisting state in registry"); |
|
return; |
|
} |
|
|
|
for (connection_t *c = o.chead; c && nactive < max_active; c = c->next) |
|
{ |
|
if (c->state == disconnected |
|
|| c->flags & FLAG_DAEMON_PERSISTENT) |
|
{ |
|
continue; |
|
} |
|
/* accumulate space needed for list of active connections */ |
|
len += wcslen(c->config_name) + 1; |
|
active_conns[nactive++] = c; |
|
} |
|
len++; /* for double nul termination */ |
|
|
|
if (len == 1) |
|
{ |
|
len++; /* two nuls for empty string */ |
|
|
|
} |
|
/* Make a double nul terminated list of active connections */ |
|
wchar_t *list = calloc(len, sizeof(wchar_t)); |
|
if (!list) |
|
{ |
|
free(active_conns); |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Out of memory while persisting state in registry"); |
|
return; |
|
} |
|
|
|
wchar_t *p = list; |
|
for (int i = 0; i < nactive; i++) |
|
{ |
|
connection_t *c = active_conns[i]; |
|
wcscpy(p, c->config_name); /* wcscpy is safe here */ |
|
p += wcslen(c->config_name) + 1; |
|
} |
|
|
|
/* Save the list in registry for auto-connect on restart */ |
|
LSTATUS status = RegSetKeyValueW(HKEY_CURRENT_USER, GUI_REGKEY_HKCU, L"auto_restart_list", |
|
REG_MULTI_SZ, list, (DWORD) len*sizeof(wchar_t)); |
|
if (status != ERROR_SUCCESS) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"RegSetKeyValue returned error: status = %lu", status); |
|
} |
|
|
|
free(active_conns); |
|
free(list); |
|
} |
|
|
|
/* |
|
* Read list of active connections in last session and set them to auto-start |
|
*/ |
|
static void |
|
LoadAutoRestartList() |
|
{ |
|
wchar_t *list; |
|
DWORD len = 0; |
|
|
|
LSTATUS status = RegGetValueW(HKEY_CURRENT_USER, GUI_REGKEY_HKCU, L"auto_restart_list", |
|
RRF_RT_REG_MULTI_SZ, NULL, NULL, &len); |
|
if (status != ERROR_SUCCESS || len == 0) |
|
{ |
|
return; |
|
} |
|
|
|
list = malloc(len); |
|
if (!list) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Out of memory while reading state from registry"); |
|
return; |
|
} |
|
|
|
status = RegGetValueW(HKEY_CURRENT_USER, GUI_REGKEY_HKCU, L"auto_restart_list", |
|
RRF_RT_REG_MULTI_SZ, NULL, list, &len); |
|
if (status != ERROR_SUCCESS) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Error reading state from registry"); |
|
free(list); |
|
return; |
|
} |
|
|
|
for (wchar_t *p = list; *p; p += wcslen(p) + 1) |
|
{ |
|
connection_t *c = GetConnByName(p); |
|
if (!c || c->flags & FLAG_DAEMON_PERSISTENT) |
|
{ |
|
continue; |
|
} |
|
c->auto_connect = true; |
|
} |
|
}
|
|
|