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.
openvpn-gui/main.c

952 lines
28 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 "passphrase.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"
Parse and display messages received by echo msg commands Process four new echo commands to construct messages to be displayed to the user: echo msg message-text echo msg-n message-text echo msg-window message-title echo msg-notify message-title Note: All rules of push and echo processing apply and determine what is received as echo commands by the GUI. In addition, 'url-encoded' characters (% followed by two hex digits) are decoded and displayed. The message is constructed in the GUI by concatenating the text specified in one or more "echo msg text" or "echo msg-n text" commands. In case of "echo msg text" text is appended with a new line. An empty text in this case will just add a new line. The message ends and gets displayed when one of the following are receieved: echo msg-window title echo msg-notify title where "title" becomes the title of the message window. In case of msg-window, a modeless window shows the message, in the latter case a notification balloon is shown. Example: when pushed from the server: push "echo msg I say let the world go to hell%2C" push "echo msg I must have my cup of tea." push "echo msg-window Notes from the underground" will display a modeless window with title "Notes from the underground" and a two line body -- I say let the world go to hell, I must have my cup of tea. -- Note that the message itself is not quoted in the above examples and so it relies on the server's option-parser combining individual words into a space separated string. Number of words on a line is limited by the maximum number of parameters allowed in openvpn commands (16). This limitation may be avoided by quoting the text that follows so that the option parser sees it as one parameter. The comma character is not allowed in pushed strings, so it has to be sent encoded as %2C as shown above. Such encoding of arbitrary bytes is suppored. For example, newlines may be embedded as %0A, though discouraged. Instead use multiple "echo msg" commands to separate lines by new line. An example with embedded spaces and multiple lines concatenated without a new line in between (note use of single quotes): push "echo msg-n I swear to you gentlemen%2C that to be" push "echo msg-n ' overly conscious is a sickness%2C ' " push "echo msg-n a real%2C thorough sickness." push "echo msg-notify Quote of the Day" will show up as a notification that displays for an OS-dependent interval as: -- Quote of the Day I swear to you gentlemen, that to be overly conscious is a sickness, a real, thorough sickness. -- where the location of the line break is automatically determined by the notification API and is OS version-dependent. Commands like "echo msg ..." in the config file are also processed the same way. It gets displayed when the GUI connects to the management interface and receives all pending echo. Pushed message(s) get displayed when the client daemon processes push-reply and passes on echo directives to the GUI. TODO: The actual window that displays the messages is implemented in the next commit. Signed-off-by: Selva Nair <selva.nair@gmail.com>
7 years ago
#include "echo.h"
#include "as.h"
#ifndef DISABLE_CHANGE_PASSWORD
#include <openssl/crypto.h>
#endif
9 years ago
#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);
void ImportConfigFileFromDisk();
void ImportConfigFromAS();
/* 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;
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;
}
9 years ago
/*
* 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);
9 years ago
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);
}
}
9 years ago
}
else
{
/* An instance is already running but its main window not yet initialized */
9 years ago
exit_code = OVPN_EXITCODE_NOTREADY;
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Previous instance not yet ready to accept commands. "
"Try again later.");
9 years ago
}
9 years ago
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;
9 years ago
BOOL first_instance = TRUE;
/* a session local semaphore to detect second instance */
HANDLE session_semaphore = InitSemaphore(L"Local\\"PACKAGE_NAME);
srand(time(NULL));
9 years ago
/* try to lock the semaphore, else we are not the first instance */
if (session_semaphore &&
WaitForSingleObject(session_semaphore, 200) != WAIT_OBJECT_0)
9 years ago
{
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
9 years ago
if (first_instance)
UpdateRegistry(); /* Checks version change and update keys/values */
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
GetRegistryKeys();
/* Parse command-line options */
ProcessCommandLine(&o, GetCommandLine());
EnsureDirExists(o.config_dir);
9 years ago
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 */
}
9 years ago
else if (o.action)
{
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"Called with --command when no previous instance available");
9 years ago
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);
}
9 years ago
GetProxyRegistrySettings();
#ifndef DISABLE_CHANGE_PASSWORD
/* Initialize OpenSSL */
set_openssl_env_vars();
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
#endif /* DISABLE_CHANGE_PASSWORD */
/* 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()
{
int i;
/* 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 (i = 0; i < o.num_configs; i++)
{
if (o.conn[i].state != disconnected && o.conn[i].state != detached)
{
if (o.conn[i].flags & FLAG_DAEMON_PERSISTENT)
{
DetachOpenVPN(&o.conn[i]);
}
else
{
StopOpenVPN(&o.conn[i]);
}
}
}
/* Wait for all connections to terminate (Max 5 sec) */
for (i = 0; i < 20; i++, Sleep(250))
{
if (CountConnState(disconnected) + CountConnState(detached) == o.num_configs)
break;
}
}
static int
AutoStartConnections()
{
int i;
for (i = 0; i < o.num_configs; i++)
{
if (o.conn[i].auto_connect && !(o.conn[i].flags & FLAG_DAEMON_PERSISTENT))
StartOpenVPN(&o.conn[i]);
}
return TRUE;
}
static void
ResumeConnections()
{
int i;
for (i = 0; i < o.num_configs; i++) {
/* Restart suspend connections */
if (o.conn[i].state == suspended)
StartOpenVPN(&o.conn[i]);
/* If some connection never reached SUSPENDED state */
if (o.conn[i].state == suspending)
StopOpenVPN(&o.conn[i]);
}
}
9 years ago
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)",
9 years ago
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)
9 years ago
{
ForceForegroundWindow(o.hWnd);
ShowWindow(c->hwndStatus, SW_SHOW);
}
else if(copy_data->dwData == WM_OVPN_STOPALL)
StopAllOpenVPN();
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);
}
else if(copy_data->dwData == WM_OVPN_IMPORT && str)
9 years ago
{
ImportConfigFile(str, true); /* prompt user */
9 years ago
}
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);
}
9 years ago
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;
9 years ago
}
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 (int i = 0; i < o.num_configs; i++)
{
if (o.conn[i].flags & FLAG_DAEMON_PERSISTENT
&& o.conn[i].auto_connect
&& (o.conn[i].state == disconnected || o.conn[i].state == detached))
{
/* disable auto-connect to avoid repeated re-connect
* after unrecoverable errors. Re-enabled on successful
* connect.
*/
o.conn[i].auto_connect = false;
o.conn[i].state = detached; /* this is required to retain management-hold on re-attach */
StartOpenVPN(&o.conn[i]); /* 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 (int i = 0; i < o.num_configs; i++)
{
if (o.conn[i].flags & FLAG_DAEMON_PERSISTENT
&& (o.conn[i].state != disconnected && o.conn[i].state != detached))
{
o.conn[i].auto_connect = false;
DetachOpenVPN(&o.conn[i]);
o.conn[i].flags |= FLAG_WAIT_UNLOCK;
}
}
}
/* Undo any actions done at session lock/disconnect.
*/
void
HandleSessionUnlock(void)
{
for (int i = 0; i < o.num_configs; i++)
{
if (o.conn[i].flags & FLAG_WAIT_UNLOCK)
{
o.conn[i].auto_connect = true; /* so that ManagePersistent will trigger attach */
o.conn[i].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;
int conn_id = 0;
MENUINFO minfo = {.cbSize = sizeof(MENUINFO)};
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));
}
9 years ago
/* Enable next line to accept WM_COPYDATA messages from lower level processes */
#if 0
ChangeWindowMessageFilterEx(hwnd, WM_COPYDATA, MSGFLT_ALLOW, NULL);
#endif
Parse and display messages received by echo msg commands Process four new echo commands to construct messages to be displayed to the user: echo msg message-text echo msg-n message-text echo msg-window message-title echo msg-notify message-title Note: All rules of push and echo processing apply and determine what is received as echo commands by the GUI. In addition, 'url-encoded' characters (% followed by two hex digits) are decoded and displayed. The message is constructed in the GUI by concatenating the text specified in one or more "echo msg text" or "echo msg-n text" commands. In case of "echo msg text" text is appended with a new line. An empty text in this case will just add a new line. The message ends and gets displayed when one of the following are receieved: echo msg-window title echo msg-notify title where "title" becomes the title of the message window. In case of msg-window, a modeless window shows the message, in the latter case a notification balloon is shown. Example: when pushed from the server: push "echo msg I say let the world go to hell%2C" push "echo msg I must have my cup of tea." push "echo msg-window Notes from the underground" will display a modeless window with title "Notes from the underground" and a two line body -- I say let the world go to hell, I must have my cup of tea. -- Note that the message itself is not quoted in the above examples and so it relies on the server's option-parser combining individual words into a space separated string. Number of words on a line is limited by the maximum number of parameters allowed in openvpn commands (16). This limitation may be avoided by quoting the text that follows so that the option parser sees it as one parameter. The comma character is not allowed in pushed strings, so it has to be sent encoded as %2C as shown above. Such encoding of arbitrary bytes is suppored. For example, newlines may be embedded as %0A, though discouraged. Instead use multiple "echo msg" commands to separate lines by new line. An example with embedded spaces and multiple lines concatenated without a new line in between (note use of single quotes): push "echo msg-n I swear to you gentlemen%2C that to be" push "echo msg-n ' overly conscious is a sickness%2C ' " push "echo msg-n a real%2C thorough sickness." push "echo msg-notify Quote of the Day" will show up as a notification that displays for an OS-dependent interval as: -- Quote of the Day I swear to you gentlemen, that to be overly conscious is a sickness, a real, thorough sickness. -- where the location of the line break is automatically determined by the notification API and is OS version-dependent. Commands like "echo msg ..." in the config file are also processed the same way. It gets displayed when the GUI connects to the management interface and receives all pending echo. Pushed message(s) get displayed when the client daemon processes push-reply and passes on echo directives to the GUI. TODO: The actual window that displays the messages is implemented in the next commit. Signed-off-by: Selva Nair <selva.nair@gmail.com>
7 years ago
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 (!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;
9 years ago
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);
}
/* rest of the handlers require a connection id */
else {
minfo.fMask = MIM_MENUDATA;
GetMenuInfo((HMENU) lParam, &minfo);
conn_id = (INT) minfo.dwMenuData;
if (conn_id < 0 || conn_id >= o.num_configs) break; /* ignore invalid connection id */
}
/* 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(&o.conn[conn_id]);
}
else if (LOWORD(wParam) == IDM_DISCONNECTMENU) {
StopOpenVPN(&o.conn[conn_id]);
}
else if (LOWORD(wParam) == IDM_RECONNECTMENU) {
RestartOpenVPN(&o.conn[conn_id]);
}
else if (LOWORD(wParam) == IDM_STATUSMENU) {
ShowWindow(o.conn[conn_id].hwndStatus, SW_SHOW);
}
else if (LOWORD(wParam) == IDM_VIEWLOGMENU) {
ViewLog(conn_id);
}
else if (LOWORD(wParam) == IDM_EDITMENU) {
EditConfig(conn_id);
}
else if (LOWORD(wParam) == IDM_CLEARPASSMENU) {
ResetSavePasswords(&o.conn[conn_id]);
}
#ifndef DISABLE_CHANGE_PASSWORD
else if (LOWORD(wParam) == IDM_PASSPHRASEMENU) {
ShowChangePassphraseDialog(&o.conn[conn_id]);
}
#endif
break;
case WM_CLOSE:
CloseApplication(hwnd);
break;
case WM_DESTROY:
WTSUnRegisterSessionNotification(hwnd);
StopAllOpenVPN();
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:
StopAllOpenVPN();
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 void
ShowSettingsDialog()
{
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
PROPSHEETPAGE psp[4];
int page_number = 0;
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
/* 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;
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
/* Advanced tab */
psp[page_number].dwSize = sizeof(PROPSHEETPAGE);
psp[page_number].dwFlags = PSP_DLGINDIRECT;
psp[page_number].hInstance = o.hInstance;
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
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.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 = NULL;
PropertySheet(&psh);
}
void
CloseApplication(HWND hwnd)
{
int i;
/* Show a message if any non-persistent connections are active */
for (i = 0; i < o.num_configs; i++)
{
if (o.conn[i].state == disconnected
|| o.conn[i].flags & FLAG_DAEMON_PERSISTENT)
{
continue;
}
/* Ask for confirmation if still connected */
if (ShowLocalizedMsgEx(MB_YESNO, NULL, _T("Exit OpenVPN"), IDS_NFO_ACTIVE_CONN_EXIT) == IDNO)
{
return;
}
break; /* show the above message box only once */
}
DestroyWindow(hwnd);
}
void
ImportConfigFileFromDisk()
{
Make options saved in registry editable by user Option ediitng dialogs are in two tabs: General and Advanced. Proxy related options are left in the proxy tab. Options config_dir, config_ext, log_dir, script timeouts and service-only flag are in the Advanced tab. All other more commonly used flags and options are in the General tab. - As options are editable, save values in registry only when they differ from the default values. This leaves the registry clean and makes changing options and their defaults during updates easier. - Entries for config_dir and log_dir must be absolute paths. Environemental variables such as %PROFILEDIR% may be used to construct these. - Empty config_dir, config_ext and log_dir entries are silently ignored (i.e., the current values are left unchanged). - Store all numeric and boolean parameters in registry as DWORD instead of strings. - On startup, the default parameters are loaded, then the registry is read and finally command-line parameters parsedi. - Out of range script timeout values in registry truncated with a warning instead of fatal error. This allows the user to access the settings dialog and make corrections. - Save proxy and language settings under the same HKCU\Software\OpenVPN-GUI key as other options instead of under Nilings. - Save the current version of the GUI in regsitry so that updates can be detected and any needed registry cleanup done. - If no version info is present in the registry any values in OpenVPN-GUI key in HKCU are deleted for a clean start as this is the first version to save registry values in HKCU. Language and proxy data if present under Nilings is migrated. Note: new controls in the General tab and newly added Advanced tab dialog are copied to all language files from the English version. These need to be translated. Signed-off-by: Selva Nair <selva.nair@gmail.com>
9 years ago
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
#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();
PostQuitMessage(exit_code);
}
else
{
exit(exit_code);
}
}