Support sending commands to running instance

- New option --command <action> <params> to send commands to
  a running instance of openvpn-gui.exe
  Supported actions are
      connect, disconnect, reconnect
  each of which takes the name of the config (with or without the
  extension .ovpn) as a parameter;
      disconnect_all, exit
  which take no parameter and
      silent_connection
  which takes an optional parameter = 0 or 1 (1 is the default)

  Examples: with the gui running, start a new instance as

  openvpn-gui.exe --command disconnect myvpn : ask running instance
                        to disconnect myvpn if connected
  openvpn-gui.exe --command status myvpn     : ask running instance
                        to show the status window for myvpn if available
  openvpn-gui.exe --command disconnect_all   : ask running instance
                        to disconnect all active connections

- The second instance exits after issuing a SendMessage to the
  already running instance. If no action is specified, the running
  instance is notified to show a balloon to alert the user

- These messages may also be sent from scripts as COPYDATA messages
  with the wData element specifying the action to execute and lpData
  a pointer to the parameter. The dwData param must be one of
  WM_OVPN_xxx with xxx = START, STOP, RESTART, STOPALL, EXIT or
  SILENT. See main.h for their values.

v2: Bug fixes based on test reports from larson0815
here: https://github.com/selvanair/openvpn-gui/issues/5
and cron410 here: https://github.com/OpenVPN/openvpn-gui/issues/104

Signed-off-by: Selva Nair <selva.nair@gmail.com>
pull/188/head
Selva Nair 2016-02-27 13:10:49 -05:00
parent 31896ce33b
commit 0f21030774
11 changed files with 251 additions and 29 deletions

156
main.c
View File

@ -55,6 +55,10 @@
#include <openssl/err.h>
#endif
#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();
@ -100,6 +104,49 @@ VerifyAutoConnections()
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 : %s", o.action, o.action_arg);
if (!SendMessageTimeout (hwnd_master, WM_COPYDATA, 0,
(LPARAM) &config_data, 0, timeout, NULL))
exit_code = OVPN_EXITCODE_TIMEOUT;
}
else
{
PrintDebug(L"Instance 2: Previous instance not yet ready to accept comamnds");
exit_code = OVPN_EXITCODE_NOTREADY;
}
PrintDebug(L"Instance 2: Returning exit code %d", exit_code);
return exit_code;
}
int WINAPI _tWinMain (HINSTANCE hThisInstance,
UNUSED HINSTANCE hPrevInstance,
@ -109,6 +156,16 @@ int WINAPI _tWinMain (HINSTANCE hThisInstance,
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);
/* try to lock the semaphore, else we are not the first instance */
if (session_semaphore &&
WaitForSingleObject(session_semaphore, 0) != WAIT_OBJECT_0)
{
first_instance = FALSE;
}
/* Initialize handlers for manangement interface notifications */
mgmt_rtmsg_handler handler[] = {
@ -166,16 +223,8 @@ int WINAPI _tWinMain (HINSTANCE hThisInstance,
PrintDebug(_T("Shell32.dll version: 0x%lx"), shell32_version);
#endif
/* Check if a previous instance is already running. */
if ((FindWindow (szClassName, NULL)) != NULL)
{
/* GUI already running */
ShowLocalizedMsg(IDS_ERR_GUI_ALREADY_RUNNING);
exit(1);
}
UpdateRegistry(); /* Checks version change and update keys/values */
if (first_instance)
UpdateRegistry(); /* Checks version change and update keys/values */
GetRegistryKeys();
/* Parse command-line options */
@ -183,6 +232,21 @@ int WINAPI _tWinMain (HINSTANCE hThisInstance,
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)
{
PrintDebug(L"Instance 1: Called with --command when no previous instance available");
exit(OVPN_EXITCODE_ERROR);
}
if (!CheckVersion()) {
exit(1);
}
@ -200,6 +264,7 @@ int WINAPI _tWinMain (HINSTANCE hThisInstance,
if (!VerifyAutoConnections()) {
exit(1);
}
GetProxyRegistrySettings();
#ifndef DISABLE_CHANGE_PASSWORD
@ -350,6 +415,68 @@ dpi_initialize(void)
dpi_setscale(dpix);
}
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: %s)",
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->hwndStatus && c)
{
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);
}
/* Not yet implemented
else if(copy_data->dwData == WM_OVPN_IMPORT)
{
}
*/
else if (copy_data->dwData == WM_OVPN_NOTIFY)
{
ShowTrayBalloon(L"", copy_data->lpData);
}
else
{
PrintDebug (L"WM_COPYDATA message ignored. (dwData: %lu, cbData: %lu)",
copy_data->dwData, copy_data->cbData);
}
return TRUE; /* indicate we handled the message */
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
@ -373,6 +500,11 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
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
CreatePopupMenus(); /* Create popup menus */
ShowTrayIcon();
if (o.service_only)
@ -387,6 +519,10 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
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_COMMAND:
if ( (LOWORD(wParam) >= IDM_CONNECTMENU) && (LOWORD(wParam) < IDM_CONNECTMENU + MAX_CONFIGS) ) {
StartOpenVPN(&o.conn[LOWORD(wParam) - IDM_CONNECTMENU]);

15
main.h
View File

@ -45,6 +45,21 @@
/* Authorized group who can use any options and config locations */
#define OVPN_ADMIN_GROUP TEXT("OpenVPN Administrators") /* May be reset in registry */
/* Application defined message IDs */
#define WM_NOTIFYICONTRAY (WM_APP + 1)
#define WM_MANAGEMENT (WM_APP + 2)
#define WM_OVPN_STOP (WM_APP + 10)
#define WM_OVPN_SUSPEND (WM_APP + 11)
#define WM_OVPN_RESTART (WM_APP + 12)
#define WM_OVPN_START (WM_APP + 13)
#define WM_OVPN_STOPALL (WM_APP + 14)
#define WM_OVPN_SHOWSTATUS (WM_APP + 15)
#define WM_OVPN_NOTIFY (WM_APP + 16)
#define WM_OVPN_EXIT (WM_APP + 17)
#define WM_OVPN_SILENT (WM_APP + 18)
#define WM_OVPN_IMPORT (WM_APP + 20)
/* bool definitions */
#define bool int
#define true 1

View File

@ -24,8 +24,6 @@
#include <winsock2.h>
#define WM_MANAGEMENT (WM_APP + 2)
typedef enum {
ready,
stop,

4
misc.c
View File

@ -396,10 +396,10 @@ BOOL IsUserAdmin(VOID)
}
HANDLE
InitSemaphore (void)
InitSemaphore (WCHAR *name)
{
HANDLE semaphore = NULL;
semaphore = CreateSemaphore (NULL, 1, 1, NULL);
semaphore = CreateSemaphore (NULL, 1, 1, name);
if (!semaphore)
{
MessageBoxW (NULL, L"Error creating semaphore", TEXT(PACKAGE_NAME), MB_OK);

2
misc.h
View File

@ -33,7 +33,7 @@ BOOL wcsbegins(LPCWSTR, LPCWSTR);
BOOL ForceForegroundWindow(HWND);
BOOL IsUserAdmin(VOID);
HANDLE InitSemaphore (void);
HANDLE InitSemaphore (WCHAR *);
BOOL CheckFileAccess (const TCHAR *path, int access);
BOOL Base64Encode(const char *input, int input_len, char **output);

View File

@ -216,6 +216,7 @@
#define IDS_NFO_ACTIVE_CONN_EXIT 1307
#define IDS_NFO_SERVICE_ACTIVE_EXIT 1308
#define IDS_ERR_CREATE_PATH 1309
#define IDS_NFO_CLICK_HERE_TO_START 1310
/* Program Options Related */
#define IDS_NFO_USAGE 1401

View File

@ -50,10 +50,6 @@
#include "access.h"
#include "save_pass.h"
#define WM_OVPN_STOP (WM_APP + 10)
#define WM_OVPN_SUSPEND (WM_APP + 11)
#define WM_OVPN_RESTART (WM_APP + 12)
extern options_t o;
static BOOL
@ -1598,6 +1594,9 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
case WM_OVPN_STOP:
c = (connection_t *) GetProp(hwndDlg, cfgProp);
/* external messages can trigger when we are not ready -- check the state */
if (!IsWindowEnabled(GetDlgItem(c->hwndStatus, ID_DISCONNECT)))
break;
c->state = disconnecting;
RunDisconnectScript(c, false);
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
@ -1605,7 +1604,7 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
SetMenuStatus(c, disconnecting);
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM));
SetEvent(c->exit_event);
SetTimer(hwndDlg, IDT_STOP_TIMER, 3000, NULL);
SetTimer(hwndDlg, IDT_STOP_TIMER, 15000, NULL);
break;
case WM_OVPN_SUSPEND:
@ -1616,7 +1615,7 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
SetMenuStatus(c, disconnecting);
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM));
SetEvent(c->exit_event);
SetTimer(hwndDlg, IDT_STOP_TIMER, 3000, NULL);
SetTimer(hwndDlg, IDT_STOP_TIMER, 15000, NULL);
break;
case WM_TIMER:
@ -1762,12 +1761,19 @@ StartOpenVPN(connection_t *c)
if (c->hwndStatus)
{
PrintDebug(L"Connection request when previous status window is still open -- ignored");
WriteStatusLog(c, L"OpenVPN GUI> ",
L"Complete the pending dialog before starting a new connection", false);
SetForegroundWindow(c->hwndStatus);
PrintDebug(L"Connection request when already started -- ignored");
/* the tread can hang around after disconnect if user has not dismissed any popups */
if (c->state == disconnected)
WriteStatusLog(c, L"OpenVPN GUI> ",
L"Complete any pending dialog before starting a new connection", false);
if (!o.silent_connection)
{
SetForegroundWindow(c->hwndStatus);
ShowWindow(c->hwndStatus, SW_SHOW);
}
return FALSE;
}
PrintDebug(L"Starting openvpn on config %s", c->config_name);
RunPreconnectScript(c);
@ -1971,7 +1977,10 @@ SuspendOpenVPN(int config)
void
RestartOpenVPN(connection_t *c)
{
PostMessage(c->hwndStatus, WM_OVPN_RESTART, 0, 0);
if (c->hwndStatus)
PostMessage(c->hwndStatus, WM_OVPN_RESTART, 0, 0);
else /* Not started: treat this as a request to connect */
StartOpenVPN(c);
}
void

View File

@ -203,6 +203,54 @@ add_option(options_t *options, int i, TCHAR **p)
++i;
options->preconnectscript_timeout = _ttoi(p[1]);
}
else if (streq(p[0], _T("command")) && p[1])
{
++i;
/* command to be sent to a running instance */
if (streq(p[1], _T("connect")) && p[2])
{
++i;
options->action = WM_OVPN_START;
options->action_arg = p[2];
}
else if (streq(p[1], _T("disconnect")) && p[2])
{
++i;
options->action = WM_OVPN_STOP;
options->action_arg = p[2];
}
else if (streq(p[1], _T("reconnect")) && p[2])
{
++i;
options->action = WM_OVPN_RESTART;
options->action_arg = p[2];
}
else if (streq(p[1], _T("status")) && p[2])
{
++i;
options->action = WM_OVPN_SHOWSTATUS;
options->action_arg = p[2];
}
else if (streq(p[1], _T("silent_connection")))
{
++i;
options->action = WM_OVPN_SILENT;
options->action_arg = p[2] ? p[2] : _T("1");
}
else if (streq(p[1], _T("disconnect_all")))
{
options->action = WM_OVPN_STOPALL;
}
else if (streq(p[1], _T("exit")))
{
options->action = WM_OVPN_EXIT;
}
else
{
ShowLocalizedMsg(IDS_ERR_BAD_OPTION, p[0]);
exit(1);
}
}
else
{
/* Unrecognized option or missing parameter */
@ -252,7 +300,7 @@ void
InitOptions(options_t *opt)
{
CLEAR(*opt);
opt->netcmd_semaphore = InitSemaphore ();
opt->netcmd_semaphore = InitSemaphore (NULL);
opt->version = MakeVersion (PACKAGE_VERSION_RESOURCE);
opt->clr_warning = RGB(0xff, 0, 0);
opt->clr_error = RGB(0xff, 0, 0);
@ -354,6 +402,18 @@ GetConnByManagement(SOCKET sk)
return NULL;
}
connection_t*
GetConnByName(const WCHAR *name)
{
for (int i = 0; i < o.num_configs; ++i)
{
if (wcsicmp (o.conn[i].config_file, name) == 0
|| wcsicmp(o.conn[i].config_name, name) == 0)
return &o.conn[i];
}
return NULL;
}
/* callback to set the initial value of folder browse selection */
static int CALLBACK
BrowseCallback (HWND h, UINT msg, UNUSED LPARAM l, LPARAM data)

View File

@ -180,12 +180,15 @@ typedef struct {
unsigned int dpi_scale;
COLORREF clr_warning;
COLORREF clr_error;
int action; /* action to send to a running instance */
TCHAR *action_arg;
} options_t;
void InitOptions(options_t *);
void ProcessCommandLine(options_t *, TCHAR *);
int CountConnState(conn_state_t);
connection_t* GetConnByManagement(SOCKET);
connection_t* GetConnByName(const WCHAR *config_name);
INT_PTR CALLBACK ScriptSettingsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK ConnectionSettingsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK AdvancedSettingsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

View File

@ -462,4 +462,6 @@ BEGIN
IDS_ERR_INVALID_PASSWORD_INPUT "Invalid character in password"
IDS_ERR_INVALID_USERNAME_INPUT "Invalid character in username"
IDS_NFO_AUTO_CONNECT "Connecting automatically in %u seconds..."
IDS_NFO_CLICK_HERE_TO_START "Right click here to start"
END

2
tray.h
View File

@ -25,8 +25,6 @@
#include "options.h"
#define WM_NOTIFYICONTRAY (WM_APP + 1)
#define IDM_SERVICE_START 100
#define IDM_SERVICE_STOP 101
#define IDM_SERVICE_RESTART 102