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.
541 lines
14 KiB
541 lines
14 KiB
/* |
|
* OpenVPN-PLAP-Provider |
|
* |
|
* Copyright (C) 2017-2022 Selva Nair <selva.nair@gmail.com> |
|
* |
|
* 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 |
|
*/ |
|
|
|
#if !defined (UNICODE) |
|
#error UNICODE and _UNICODE must be defined. This version only supports unicode builds. |
|
#endif |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include <config.h> |
|
#endif |
|
|
|
#include "plap_common.h" |
|
#include "ui_glue.h" |
|
#include "main.h" |
|
#include "options.h" |
|
#include "openvpn.h" |
|
#include "manage.h" |
|
#include "openvpn_config.h" |
|
#include "proxy.h" |
|
#include "registry.h" |
|
#include "openvpn-gui-res.h" |
|
#include "localization.h" |
|
#include "misc.h" |
|
#include "tray.h" |
|
#include "service.h" |
|
|
|
/* Global options structure */ |
|
options_t o; |
|
|
|
int state_connected = connected, state_disconnected = disconnected, |
|
state_onhold = onhold; |
|
|
|
static connection_t *active_profile; |
|
DWORD status_menu_id = IDM_STATUSMENU; |
|
|
|
/* Override management handlers that generate user dialogs |
|
* and pass them on only for currently active profile. |
|
* Also ensure no unwanted popus are generated. |
|
*/ |
|
static void |
|
OnStop_(connection_t *c, UNUSED char *msg) |
|
{ |
|
dmsg(L"profile: %ls with state = %d", c->config_name, c->state); |
|
|
|
/* do not show any popup error messages */ |
|
c->state = disconnected; |
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_DISCONNECTED)); |
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_DISCONNECTED); |
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0); |
|
} |
|
|
|
/* Override OnInfoMsg: We filter out anything other |
|
* than CR_TEXT: In particular, OPEN_URL is not supported |
|
* in PLAP context. |
|
*/ |
|
static void |
|
OnInfoMsg_(connection_t* c, char* msg) |
|
{ |
|
if (strbegins(msg, "CR_TEXT:")) |
|
{ |
|
if (c == active_profile) |
|
{ |
|
OnInfoMsg(c, msg); |
|
} |
|
else |
|
{ |
|
DetachOpenVPN(c); /* next attach will handle it */ |
|
} |
|
} |
|
} |
|
|
|
static void |
|
OnNeedOk_(connection_t *c, char *msg) |
|
{ |
|
if (c == active_profile) |
|
{ |
|
OnNeedOk(c, msg); |
|
} |
|
else |
|
{ |
|
DetachOpenVPN(c); /* next attach will handle it */ |
|
} |
|
} |
|
|
|
static void |
|
OnNeedStr_(connection_t *c, char *msg) |
|
{ |
|
if (c == active_profile) |
|
{ |
|
OnNeedStr(c, msg); |
|
} |
|
else |
|
{ |
|
DetachOpenVPN(c); /* next attach will handle it */ |
|
} |
|
} |
|
|
|
static void |
|
OnPassword_(connection_t *c, char *msg) |
|
{ |
|
if (c == active_profile) |
|
{ |
|
OnPassword(c, msg); |
|
} |
|
else |
|
{ |
|
DetachOpenVPN(c); /* next attach will handle it */ |
|
} |
|
} |
|
|
|
static void |
|
OnProxy_(connection_t *c, char *msg) |
|
{ |
|
if (c == active_profile) |
|
{ |
|
OnProxy(c, msg); |
|
} |
|
else |
|
{ |
|
DetachOpenVPN(c); /* next attach will handle it */ |
|
} |
|
} |
|
|
|
/* Intercept state change. Keep track of previous state and handle |
|
* cases like user wants to disconnect but connection completed in |
|
* the meantime. |
|
*/ |
|
void |
|
OnStateChange_(connection_t *c, char *msg) |
|
{ |
|
int state_prev = c->state; |
|
|
|
OnStateChange(c, msg); |
|
|
|
if (c->state == connected && state_prev == disconnecting) |
|
{ |
|
/* connection completed while user clicked disconnect, |
|
* let disconnect process continue. This is required to |
|
* retain the hold state after SIGHUP restart. |
|
*/ |
|
c->state = disconnecting; |
|
} |
|
} |
|
|
|
/* Initialize GUI data structures. Returns 0 on success */ |
|
DWORD |
|
InitializeUI(HINSTANCE hinstance) |
|
{ |
|
WSADATA wsaData; |
|
|
|
/* a session local semaphore to detect second instance */ |
|
HANDLE session_semaphore = InitSemaphore(L"Local\\"PACKAGE_NAME"-PLAP"); |
|
|
|
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) |
|
{ |
|
if (hinstance == o.hInstance) |
|
{ |
|
/* already initialized */ |
|
return 0; |
|
} |
|
else |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"InitializeUI called a second time with a different hinstance -- multiple instances of the UI not supported."); |
|
return 1; |
|
} |
|
} |
|
|
|
dmsg(L"Starting OpenVPN UI v%hs", PACKAGE_VERSION); |
|
|
|
if(!GetModuleHandle(_T("RICHED20.DLL"))) |
|
{ |
|
LoadLibrary(_T("RICHED20.DLL")); |
|
} |
|
else |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, LoadLocalizedString(IDS_ERR_LOAD_RICHED20)); |
|
return 1; |
|
} |
|
|
|
/* Initialize handlers for management interface notifications |
|
* Some handlers are replaced by local functions |
|
*/ |
|
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); |
|
dmsg(L"Init Management done"); |
|
|
|
/* initialize options to default state */ |
|
InitOptions(&o); |
|
o.session_semaphore = session_semaphore; |
|
|
|
dmsg(L"InitOptions done"); |
|
|
|
GetRegistryKeys(); |
|
|
|
dmsg(L"GetRegistryKeys done"); |
|
|
|
/* Do not show status window by default */ |
|
o.silent_connection = 1; |
|
o.disable_save_passwords = 1; |
|
o.disable_popup_messages = 1; |
|
o.enable_persistent = 1; |
|
/* In case we get queried for proxy, we currently support only system proxy */ |
|
o.proxy_source = windows; |
|
|
|
/* Force scanning persistent connections -- we still need the service |
|
* running, but in case the service start-up is delayed, this helps |
|
* as we scan for profiles only once during a login session. |
|
* We expect users who register the PLAP dll to also enable the service. |
|
*/ |
|
o.service_state = service_connected; |
|
|
|
o.hInstance = hinstance; |
|
|
|
DWORD status = WSAStartup(MAKEWORD(2, 2), &wsaData); |
|
if (status) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"WSAStartup Failed with error = %lu", status); |
|
return status; |
|
} |
|
dmsg(L"WSAStartup Done"); |
|
|
|
BuildFileList(); |
|
dmsg(L"BuildFileList Done"); |
|
|
|
dpi_initialize(&o); |
|
|
|
/* If openvpn service is not running but, available, attempt to start it */ |
|
CheckServiceStatus(); |
|
int num_persistent = 0; |
|
|
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->flags & FLAG_DAEMON_PERSISTENT) num_persistent++; |
|
} |
|
|
|
if (o.service_state == service_disconnected && num_persistent > 0) { |
|
dmsg(L"Attempting to start automatic service"); |
|
StartAutomaticService(); |
|
CheckServiceStatus(); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Returns number of PLAP enabled configs -- for now |
|
* same as autostarted (persistent) connections. |
|
* The corresponding connection pointers are set in |
|
* the conn[] array |
|
*/ |
|
DWORD |
|
FindPLAPConnections(connection_t *conn[], size_t max_count) |
|
{ |
|
DWORD count = 0; |
|
for (connection_t *c = o.chead; c && count < max_count; c = c->next) |
|
{ |
|
if (!(c->flags & FLAG_DAEMON_PERSISTENT) |
|
|| !ParseManagementAddress(c)) |
|
{ |
|
continue; |
|
} |
|
conn[count++] = c; |
|
} |
|
return count; |
|
} |
|
|
|
static void |
|
WaitOnThread (connection_t *c, DWORD timeout) |
|
{ |
|
HANDLE h = OpenThread(THREAD_ALL_ACCESS, FALSE, c->threadId); |
|
if (!h) |
|
{ |
|
dmsg(L"Failed to get handle to the connection thread"); |
|
goto out; |
|
} |
|
|
|
DWORD exit_code; |
|
if (WaitForSingleObject(h, timeout) == WAIT_OBJECT_0 && |
|
GetExitCodeThread(h, &exit_code) && exit_code != STILL_ACTIVE) |
|
{ |
|
dmsg(L"Connection thread closed"); |
|
goto out; |
|
} |
|
|
|
/* Kill the thread */ |
|
dmsg(L"Force terminating a connection thread"); |
|
TerminateThread (h, 1); |
|
c->hwndStatus = NULL; |
|
c->threadId = 0; |
|
|
|
out: |
|
if (h && h != INVALID_HANDLE_VALUE) |
|
{ |
|
CloseHandle(h); |
|
} |
|
return; |
|
} |
|
|
|
void |
|
GetConnectionStatusText(connection_t *c, wchar_t *status, DWORD len) |
|
{ |
|
wchar_t status_text[256] = L""; |
|
|
|
/* blank status as a default */ |
|
if (len > 0) |
|
{ |
|
*status = '\0'; |
|
} |
|
|
|
if (c->hwndStatus) |
|
{ |
|
GetDlgItemText(c->hwndStatus, ID_TXT_STATUS, status_text, _countof(status_text)); |
|
/* showing RECONNECTING while on hold is confusing, use status text */ |
|
if ((strcmp(c->daemon_state, "RECONNECTING") == 0) && c->state == onhold && *status_text) |
|
{ |
|
wcsncpy_s(status, len, status_text, _TRUNCATE); |
|
} |
|
else if (*c->daemon_state) /* this is more fine-grained and thus preferred */ |
|
{ |
|
LoadLocalizedStringBuf(status, len, daemon_state_resid(c->daemon_state)); |
|
} |
|
else if (*status_text) |
|
{ |
|
wcsncpy_s(status, len, status_text, _TRUNCATE); |
|
} |
|
} |
|
} |
|
|
|
void |
|
SetParentWindow(HWND hwnd) |
|
{ |
|
o.hWnd = hwnd; |
|
} |
|
|
|
void |
|
ShowStatusWindow(connection_t *c, BOOL show) |
|
{ |
|
if (c->hwndStatus) |
|
{ |
|
/* Do not enable detach button in the PLAP mode */ |
|
ShowWindowAsync(GetDlgItem(c->hwndStatus, ID_DETACH), SW_HIDE); |
|
/* Disconnecting from status Window gives no feedback to progress dialog. |
|
* Do not show the disconnect button. |
|
*/ |
|
ShowWindowAsync(GetDlgItem(c->hwndStatus, ID_DISCONNECT), SW_HIDE); |
|
ShowWindowAsync(c->hwndStatus, show ? SW_SHOW : SW_HIDE); |
|
} |
|
} |
|
|
|
|
|
void |
|
DetachAllOpenVPN() |
|
{ |
|
int i; |
|
|
|
/* Detach from the mgmt i/f of all connections */ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->state != disconnected) |
|
{ |
|
DetachOpenVPN(c); |
|
} |
|
} |
|
|
|
/* Wait for all connections to detach (Max 1 sec) */ |
|
for (i = 0; i < 10; i++) |
|
{ |
|
if (CountConnState(disconnected) == o.num_configs) |
|
{ |
|
break; |
|
} |
|
OVPNMsgWait(100, NULL); |
|
} |
|
|
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (c->hwndStatus) |
|
{ |
|
/* Status thread still running? kill it */ |
|
WaitOnThread(c, 0); |
|
} |
|
} |
|
} |
|
|
|
const wchar_t * |
|
ConfigDisplayName(connection_t *c) |
|
{ |
|
return c->config_name; |
|
} |
|
|
|
int |
|
ConnectionState(connection_t *c) |
|
{ |
|
return c->state; |
|
} |
|
|
|
void |
|
DeleteUI(void) |
|
{ |
|
if (!o.hInstance) /* not initialized? */ |
|
{ |
|
dmsg(L"DeleteUI called before InitializeUI"); |
|
} |
|
DetachAllOpenVPN(); |
|
/* at this point all status threads have terminated -- we can safely free config list */ |
|
FreeConfigList(&o); |
|
CloseSemaphore(o.session_semaphore); |
|
WSACleanup(); |
|
memset (&o, 0, sizeof(o)); |
|
} |
|
|
|
void |
|
ConnectHelper(connection_t *c) |
|
{ |
|
if (c->state == disconnected || c->state == detached) |
|
{ |
|
StartOpenVPN(c); |
|
dmsg(L"Calling StartOpenVPN on <%ls>", c->config_name); |
|
} |
|
else if (c->state == onhold) |
|
{ |
|
ReleaseOpenVPN(c); |
|
dmsg(L"Calling ReleaseOpenVPN on <%ls>", c->config_name); |
|
} |
|
} |
|
|
|
void |
|
DisconnectHelper(connection_t *c) |
|
{ |
|
if (c->state == disconnected || c->state == onhold |
|
|| c->manage.connected < 2) /* mgmt not yet ready for input */ |
|
{ |
|
return; |
|
} |
|
|
|
/* disconnect will not work if disconnect button is disabled on the status |
|
* window -- enable it until out of here. |
|
*/ |
|
ShowWindowAsync(GetDlgItem(c->hwndStatus, ID_DISCONNECT), SW_SHOW); |
|
|
|
dmsg(L"sending stop"); |
|
StopOpenVPN(c); |
|
|
|
/* wait up to a few sec for state to change */ |
|
time_t timeout = time(NULL) + 5; |
|
while (timeout > time(NULL) && c->state != onhold && c->state != disconnected) |
|
{ |
|
OVPNMsgWait(100, NULL); |
|
} |
|
|
|
ShowWindowAsync(GetDlgItem(c->hwndStatus, ID_DISCONNECT), SW_HIDE); |
|
|
|
dmsg(L"profile: %ls state = %d", c->config_name, c->state); |
|
} |
|
|
|
/* Set the currently active profile in Connect() operation -- pass NULL to unset. |
|
* We allow UI dialogs only on the current profile. |
|
*/ |
|
void |
|
SetActiveProfile(connection_t *c) |
|
{ |
|
active_profile = c; |
|
} |
|
|
|
int |
|
RunProgressDialog(connection_t *c, PFTASKDIALOGCALLBACK cb_fn, LONG_PTR cb_data) |
|
{ |
|
dmsg(L"Entry with profile = <%ls>", c->config_name); |
|
|
|
TASKDIALOG_FLAGS flags = TDF_SHOW_MARQUEE_PROGRESS_BAR|TDF_CALLBACK_TIMER|TDF_USE_HICON_MAIN; |
|
wchar_t main_text[256]; |
|
wchar_t details_btn_text[256]; |
|
|
|
if (LangFlowDirection() == 1) |
|
{ |
|
flags |= TDF_RTL_LAYOUT; |
|
} |
|
_sntprintf_0(main_text, L"%ls %ls", LoadLocalizedString(IDS_MENU_CONNECT), c->config_name); |
|
LoadLocalizedStringBuf(details_btn_text, _countof(details_btn_text), IDS_MENU_STATUS); |
|
|
|
const TASKDIALOG_BUTTON extra_buttons[] = { |
|
{status_menu_id, details_btn_text}, |
|
}; |
|
|
|
const TASKDIALOGCONFIG taskcfg = { |
|
.cbSize = sizeof(taskcfg), |
|
.hwndParent = o.hWnd, |
|
.hInstance = o.hInstance, |
|
.dwFlags = flags, |
|
.hMainIcon = LoadLocalizedIcon(ID_ICO_APP), |
|
.cButtons = _countof(extra_buttons), |
|
.pButtons = extra_buttons, |
|
.dwCommonButtons = TDCBF_CANCEL_BUTTON|TDCBF_RETRY_BUTTON, |
|
.pszWindowTitle = L""PACKAGE_NAME" PLAP", |
|
.pszMainInstruction = main_text, |
|
.pszContent = L"Starting", /* updated in progress callback */ |
|
.pfCallback = cb_fn, |
|
.lpCallbackData = cb_data, |
|
}; |
|
|
|
int button_clicked = 0; |
|
dmsg(L"calling taskdialogindirect"); |
|
TaskDialogIndirect(&taskcfg, &button_clicked, NULL, NULL); |
|
return button_clicked; |
|
}
|
|
|