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

1164 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;
}
}