|
|
|
/*
|
|
|
|
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Mathias Sundman <mathias@nilings.se>
|
|
|
|
* 2010 Heiko Hund <heikoh@users.sf.net>
|
|
|
|
* 2016 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
#include <windowsx.h>
|
|
|
|
#include <versionhelpers.h>
|
|
|
|
#include <tchar.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <process.h>
|
|
|
|
#include <richedit.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <commctrl.h>
|
|
|
|
|
|
|
|
#ifndef WM_DPICHANGED
|
|
|
|
#define WM_DPICHANGED 0x02E0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef ENABLE_OVPN3
|
|
|
|
#include <json-c/json.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "tray.h"
|
|
|
|
#include "main.h"
|
|
|
|
#include "openvpn.h"
|
|
|
|
#include "openvpn_config.h"
|
|
|
|
#include "openvpn-gui-res.h"
|
|
|
|
#include "options.h"
|
|
|
|
#include "scripts.h"
|
|
|
|
#include "viewlog.h"
|
|
|
|
#include "proxy.h"
|
|
|
|
#include "passphrase.h"
|
|
|
|
#include "localization.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "access.h"
|
|
|
|
#include "save_pass.h"
|
|
|
|
#include "env_set.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 "pkcs11.h"
|
|
|
|
|
|
|
|
#define OPENVPN_SERVICE_PIPE_NAME_OVPN2 L"\\\\.\\pipe\\openvpn\\service"
|
|
|
|
#define OPENVPN_SERVICE_PIPE_NAME_OVPN3 L"\\\\.\\pipe\\ovpnagent"
|
|
|
|
|
|
|
|
extern options_t o;
|
|
|
|
|
|
|
|
static BOOL TerminateOpenVPN(connection_t *c);
|
|
|
|
static BOOL LaunchOpenVPN(connection_t *c);
|
|
|
|
|
|
|
|
const TCHAR *cfgProp = _T("conn");
|
|
|
|
|
|
|
|
void
|
|
|
|
free_auth_param (auth_param_t *param)
|
|
|
|
{
|
|
|
|
if (!param)
|
|
|
|
return;
|
|
|
|
free (param->str);
|
|
|
|
free (param->id);
|
|
|
|
free (param->user);
|
|
|
|
free (param->cr_response);
|
|
|
|
free (param);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
AppendTextToCaption (HANDLE hwnd, const WCHAR *str)
|
|
|
|
{
|
|
|
|
WCHAR old[256];
|
|
|
|
WCHAR new[256];
|
|
|
|
GetWindowTextW (hwnd, old, _countof(old));
|
|
|
|
_sntprintf_0 (new, L"%ls (%ls)", old, str);
|
|
|
|
SetWindowText (hwnd, new);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show an error tooltip with msg attached to the specified
|
|
|
|
* editbox handle.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_error_tip(HWND editbox, const WCHAR *msg)
|
|
|
|
{
|
|
|
|
EDITBALLOONTIP bt;
|
|
|
|
bt.cbStruct = sizeof(EDITBALLOONTIP);
|
|
|
|
bt.pszText = msg;
|
|
|
|
bt.pszTitle = L"Invalid input";
|
|
|
|
bt.ttiIcon = TTI_ERROR_LARGE;
|
|
|
|
|
|
|
|
SendMessage(editbox, EM_SHOWBALLOONTIP, 0, (LPARAM)&bt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Receive banner on connection to management interface
|
|
|
|
* Format: <BANNER>
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnReady(connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
ManagementCommand(c, "state on", NULL, regular);
|
|
|
|
ManagementCommand(c, "log on all", OnLogLine, combined);
|
|
|
|
ManagementCommand(c, "echo on all", OnEcho, combined);
|
|
|
|
ManagementCommand(c, "bytecount 5", NULL, regular);
|
|
|
|
|
|
|
|
/* ask for the current state, especially useful when the daemon was prestarted */
|
|
|
|
ManagementCommand(c, "state", OnStateChange, regular);
|
|
|
|
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT
|
|
|
|
&& o.enable_persistent == 2)
|
|
|
|
{
|
|
|
|
c->auto_connect = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle the request to release a hold from the OpenVPN management interface
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnHold(connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), TRUE);
|
|
|
|
if ((c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
&& (c->state == disconnecting || c->state == resuming))
|
|
|
|
{
|
|
|
|
/* retain the hold state if we are here while disconnecting */
|
|
|
|
c->state = onhold;
|
|
|
|
SetMenuStatus(c, onhold);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_ONHOLD));
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_DISCONNECTED);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), TRUE);
|
|
|
|
ManagementCommand(c, "hold off", NULL, regular);
|
|
|
|
ManagementCommand(c, "hold release", NULL, regular);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a log line from the OpenVPN management interface
|
|
|
|
* Format <TIMESTAMP>,<FLAGS>,<MESSAGE>
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnLogLine(connection_t *c, char *line)
|
|
|
|
{
|
|
|
|
HWND logWnd = GetDlgItem(c->hwndStatus, ID_EDT_LOG);
|
|
|
|
char *flags, *message;
|
|
|
|
time_t timestamp;
|
|
|
|
TCHAR *datetime;
|
|
|
|
const SETTEXTEX ste = {
|
|
|
|
.flags = ST_SELECTION,
|
|
|
|
.codepage = CP_UTF8
|
|
|
|
};
|
|
|
|
|
|
|
|
flags = strchr(line, ',') + 1;
|
|
|
|
if (flags - 1 == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
message = strchr(flags, ',') + 1;
|
|
|
|
if (message - 1 == NULL)
|
|
|
|
return;
|
|
|
|
size_t flag_size = message - flags - 1; /* message is always > flags */
|
|
|
|
|
|
|
|
/* Remove lines from log window if it is getting full */
|
|
|
|
if (SendMessage(logWnd, EM_GETLINECOUNT, 0, 0) > MAX_LOG_LINES)
|
|
|
|
{
|
|
|
|
int pos = SendMessage(logWnd, EM_LINEINDEX, DEL_LOG_LINES, 0);
|
|
|
|
SendMessage(logWnd, EM_SETSEL, 0, pos);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) _T(""));
|
|
|
|
}
|
|
|
|
|
|
|
|
timestamp = strtol(line, NULL, 10);
|
|
|
|
datetime = _tctime(×tamp);
|
|
|
|
datetime[24] = _T(' ');
|
|
|
|
|
|
|
|
/* deselect current selection, if any */
|
|
|
|
SendMessage(logWnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1);
|
|
|
|
|
|
|
|
/* change text color if Warning or Error */
|
|
|
|
COLORREF text_clr = 0;
|
|
|
|
|
|
|
|
if (memchr(flags, 'N', flag_size) || memchr(flags, 'F', flag_size))
|
|
|
|
text_clr = o.clr_error;
|
|
|
|
else if (memchr(flags, 'W', flag_size))
|
|
|
|
text_clr = o.clr_warning;
|
|
|
|
|
|
|
|
if (text_clr != 0)
|
|
|
|
{
|
|
|
|
CHARFORMAT cfm = { .cbSize = sizeof(CHARFORMAT),
|
|
|
|
.dwMask = CFM_COLOR|CFM_BOLD,
|
|
|
|
.dwEffects = 0,
|
|
|
|
.crTextColor = text_clr,
|
|
|
|
};
|
|
|
|
SendMessage(logWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cfm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Append line to log window */
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) datetime);
|
|
|
|
SendMessage(logWnd, EM_SETTEXTEX, (WPARAM) &ste, (LPARAM) message);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) _T("\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* expect ipv4,remote,port,,,ipv6 */
|
|
|
|
static void
|
|
|
|
parse_assigned_ip(connection_t *c, const char *msg)
|
|
|
|
{
|
|
|
|
char *sep;
|
|
|
|
CLEAR(c->ip);
|
|
|
|
CLEAR(c->ipv6);
|
|
|
|
|
|
|
|
/* extract local ipv4 address if available*/
|
|
|
|
sep = strchr(msg, ',');
|
|
|
|
if (sep == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Convert the IP address to Unicode */
|
|
|
|
if (sep - msg > 0
|
|
|
|
&& MultiByteToWideChar(CP_UTF8, 0, msg, sep-msg, c->ip, _countof(c->ip)-1) == 0)
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Failed to extract the assigned ipv4 address (error = %d)",
|
|
|
|
GetLastError());
|
|
|
|
c->ip[0] = L'\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* extract local ipv6 address if available */
|
|
|
|
|
|
|
|
/* skip 4 commas */
|
|
|
|
for (int i = 0; i < 4 && sep; i++)
|
|
|
|
{
|
|
|
|
sep = strchr(sep + 1, ',');
|
|
|
|
}
|
|
|
|
if (!sep)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sep++; /* start of ipv6 address */
|
|
|
|
/* Convert the IP address to Unicode */
|
|
|
|
if (MultiByteToWideChar(CP_UTF8, 0, sep, -1, c->ipv6, _countof(c->ipv6)-1) == 0)
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Failed to extract the assigned ipv6 address (error = %d)",
|
|
|
|
GetLastError());
|
|
|
|
c->ipv6[0] = L'\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send a custom message to Window hwnd when state changes
|
|
|
|
* hwnd : handle of the window to which the message is sent
|
|
|
|
* lParam : pointer to the state string (char *) received from
|
|
|
|
* OpenVPN daemon (e., "CONNECTED")
|
|
|
|
* The function signature matches callback for EnumThreadWindows.
|
|
|
|
*/
|
|
|
|
BOOL CALLBACK
|
|
|
|
NotifyStateChange(HWND hwnd, LPARAM lParam)
|
|
|
|
{
|
|
|
|
SendMessage(hwnd, WM_OVPN_STATE, 0, lParam);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a state change notification from the OpenVPN management interface
|
|
|
|
* Format <TIMESTAMP>,<STATE>,[<MESSAGE>],[<LOCAL_IP>][,<REMOTE_IP>]
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnStateChange(connection_t *c, char *data)
|
|
|
|
{
|
|
|
|
char *pos, *state, *message;
|
|
|
|
|
|
|
|
if (data == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pos = strchr(data, ',');
|
|
|
|
if (pos == NULL)
|
|
|
|
return;
|
|
|
|
*pos = '\0';
|
|
|
|
|
|
|
|
state = pos + 1;
|
|
|
|
pos = strchr(state, ',');
|
|
|
|
if (pos == NULL)
|
|
|
|
return;
|
|
|
|
*pos = '\0';
|
|
|
|
|
|
|
|
message = pos + 1;
|
|
|
|
pos = strchr(message, ',');
|
|
|
|
if (pos == NULL)
|
|
|
|
return;
|
|
|
|
*pos = '\0';
|
|
|
|
|
|
|
|
/* notify the all windows in the thread of state change */
|
|
|
|
EnumThreadWindows(GetCurrentThreadId(), NotifyStateChange, (LPARAM) state);
|
|
|
|
|
|
|
|
strncpy_s(c->daemon_state, _countof(c->daemon_state), state, _TRUNCATE);
|
|
|
|
|
|
|
|
/* Connected state message could be SUCCESS or ERROR, ROUTE_ERROR.
|
|
|
|
* We treat both SUCCESS and ROUTE_ERROR similarly to preserve the
|
|
|
|
* current behaviour but show the status window and do not change
|
|
|
|
* icons to green if not "SUCCESS".
|
|
|
|
*/
|
|
|
|
if (strcmp(state, "CONNECTED") == 0)
|
|
|
|
{
|
|
|
|
bool success = !strcmp(message, "SUCCESS");
|
|
|
|
|
|
|
|
parse_assigned_ip(c, pos + 1);
|
|
|
|
|
|
|
|
if (!success) /* connection completed with errors */
|
|
|
|
{
|
|
|
|
SetForegroundWindow(c->hwndStatus);
|
|
|
|
ShowWindow(c->hwndStatus, SW_SHOW);
|
|
|
|
/* The daemon does not currently log this error. Add a line to the status window */
|
|
|
|
if (!strcmp(message, "ROUTE_ERROR"))
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"ERROR: ", L"Some routes were not successfully added. The connection may not function correctly", false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"ERROR: ", L"Connection completed with critical errors.", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* concatenate ipv4 and ipv6 addresses into one string */
|
|
|
|
WCHAR ip_txt[256];
|
|
|
|
WCHAR ip[64];
|
|
|
|
wcs_concat2(ip, _countof(ip), c->ip, c->ipv6, L", ");
|
|
|
|
LoadLocalizedStringBuf(ip_txt, _countof(ip_txt), IDS_NFO_ASSIGN_IP, ip);
|
|
|
|
|
|
|
|
/* Run Connect Script */
|
|
|
|
if (!(c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
&& (c->state == connecting || c->state == resuming))
|
|
|
|
{
|
|
|
|
RunConnectScript(c, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Show connection tray balloon */
|
|
|
|
if ((c->state == connecting && o.show_balloon != 0)
|
|
|
|
|| (c->state == resuming && o.show_balloon != 0)
|
|
|
|
|| (c->state == reconnecting && o.show_balloon == 2))
|
|
|
|
{
|
|
|
|
TCHAR msg[256];
|
|
|
|
DWORD id = success ? IDS_NFO_NOW_CONNECTED : IDS_NFO_NOTIFY_ROUTE_ERROR;
|
|
|
|
LoadLocalizedStringBuf(msg, _countof(msg), id, c->config_name);
|
|
|
|
ShowTrayBalloon(msg, (ip[0] ? ip_txt : _T("")));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Save time when we got connected. */
|
|
|
|
c->connected_since = atoi(data);
|
|
|
|
c->failed_psw_attempts = 0;
|
|
|
|
c->failed_auth_attempts = 0;
|
|
|
|
c->state = connected;
|
|
|
|
|
|
|
|
SetMenuStatus(c, connected);
|
|
|
|
SetTrayIcon(connected);
|
|
|
|
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_CONNECTED));
|
|
|
|
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, ip_txt);
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_ROUTE_ERROR));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTED);
|
|
|
|
|
|
|
|
/* Hide Status Window */
|
|
|
|
ShowWindow(c->hwndStatus, SW_HIDE);
|
|
|
|
}
|
|
|
|
else if (strcmp(state, "RECONNECTING") == 0)
|
|
|
|
{
|
|
|
|
if (!c->dynamic_cr)
|
|
|
|
{
|
|
|
|
if (strcmp(message, "auth-failure") == 0)
|
|
|
|
c->failed_auth_attempts++;
|
|
|
|
else if (strcmp(message, "private-key-password-failure") == 0)
|
|
|
|
c->failed_psw_attempts++;
|
|
|
|
}
|
|
|
|
|
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_clear(c, false); /* do not clear history */
|
|
|
|
// We change the state to reconnecting only if there was a prior successful connection.
|
|
|
|
if (c->state == connected)
|
|
|
|
{
|
|
|
|
c->state = reconnecting;
|
|
|
|
|
|
|
|
// Update the tray icon
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
|
|
|
|
// And the texts in the status window
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING));
|
|
|
|
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
SimulateButtonPress(HWND hwnd, UINT btn)
|
|
|
|
{
|
|
|
|
SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(btn, BN_CLICKED), (LPARAM)GetDlgItem(hwnd, btn));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A private struct to keep autoclose parameters */
|
|
|
|
typedef struct autoclose {
|
|
|
|
UINT start; /* start time in msec */
|
|
|
|
UINT timeout; /* timeout in msec at which autoclose triggers */
|
|
|
|
UINT btn; /* button which is 'pressed' for autoclose */
|
|
|
|
UINT txtid; /* id of a text control to display an informaltional message */
|
|
|
|
UINT txtres; /* resource id of localized text to use for message:
|
|
|
|
LoadLocalizedString(txtid, time_remaining_in_seconds)
|
|
|
|
is used to generate the message */
|
|
|
|
COLORREF txtclr; /* color for message text */
|
|
|
|
} autoclose;
|
|
|
|
|
|
|
|
/* Cancel scheduled auto close of a dialog */
|
|
|
|
static void
|
|
|
|
AutoCloseCancel(HWND hwnd)
|
|
|
|
{
|
|
|
|
autoclose *ac = (autoclose *)GetProp(hwnd, L"AutoClose");
|
|
|
|
if (!ac)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (ac->txtid)
|
|
|
|
SetDlgItemText(hwnd, ac->txtid, L"");
|
|
|
|
KillTimer(hwnd, 1);
|
|
|
|
RemoveProp(hwnd, L"AutoClose");
|
|
|
|
free(ac);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when autoclose timer expires. */
|
|
|
|
static void CALLBACK
|
|
|
|
AutoCloseHandler(HWND hwnd, UINT UNUSED msg, UINT_PTR id, DWORD now)
|
|
|
|
{
|
|
|
|
autoclose *ac = (autoclose *)GetProp(hwnd, L"AutoClose");
|
|
|
|
if (!ac)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DWORD elapsed = now - ac->start;
|
|
|
|
if (elapsed >= ac->timeout)
|
|
|
|
{
|
|
|
|
SimulateButtonPress(hwnd, ac->btn);
|
|
|
|
AutoCloseCancel(hwnd);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetDlgItemText(hwnd, ac->txtid, LoadLocalizedString(ac->txtres, (ac->timeout - elapsed)/1000));
|
|
|
|
SetTimer(hwnd, id, 500, AutoCloseHandler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Schedule an event to automatically activate specified btn after timeout seconds.
|
|
|
|
* Used for automatically closing a dialog after some delay unless user interrupts.
|
|
|
|
* Optionally a txtid and txtres resource may be specified to display a message.
|
|
|
|
* %u in the message is replaced by time remaining before timeout will trigger.
|
|
|
|
* Call AutoCloseCancel to cancel the scheduled event and clear the displayed
|
|
|
|
* text.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
AutoCloseSetup(HWND hwnd, UINT btn, UINT timeout, UINT txtid, UINT txtres)
|
|
|
|
{
|
|
|
|
if (timeout == 0)
|
|
|
|
SimulateButtonPress(hwnd, btn);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
autoclose *ac = GetPropW(hwnd, L"AutoClose"); /* reuse if set */
|
|
|
|
if (!ac && (ac = malloc(sizeof(autoclose))) == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
*ac = (autoclose) {.start = GetTickCount(), .timeout = timeout*1000, .btn = btn,
|
|
|
|
.txtid = txtid, .txtres = txtres, .txtclr = GetSysColor(COLOR_WINDOWTEXT)};
|
|
|
|
|
|
|
|
SetTimer(hwnd, 1, 500, AutoCloseHandler); /* using timer id = 1 */
|
|
|
|
if (txtid && txtres)
|
|
|
|
SetDlgItemText(hwnd, txtid, LoadLocalizedString(txtres, timeout));
|
|
|
|
SetPropW(hwnd, L"AutoClose", (HANDLE) ac); /* failure not critical */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DialogProc for OpenVPN username/password/challenge auth dialog windows
|
|
|
|
*/
|
|
|
|
INT_PTR CALLBACK
|
|
|
|
UserAuthDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
auth_param_t *param;
|
|
|
|
WCHAR username[USER_PASS_LEN] = L"";
|
|
|
|
WCHAR password[USER_PASS_LEN] = L"";
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case WM_INITDIALOG:
|
|
|
|
/* Set connection for this dialog and show it */
|
|
|
|
param = (auth_param_t *) lParam;
|
|
|
|
TRY_SETPROP(hwndDlg, cfgProp, (HANDLE) param);
|
|
|
|
SetStatusWinIcon(hwndDlg, ID_ICO_APP);
|
|
|
|
|
|
|
|
if (param->str)
|
|
|
|
{
|
|
|
|
LPWSTR wstr = Widen (param->str);
|
|
|
|
HWND wnd_challenge = GetDlgItem(hwndDlg, ID_EDT_AUTH_CHALLENGE);
|
|
|
|
|
|
|
|
if (!wstr)
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Error converting challenge string to widechar", false);
|
|
|
|
else
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_TXT_AUTH_CHALLENGE, wstr);
|
|
|
|
|
|
|
|
free(wstr);
|
|
|
|
|
|
|
|
/* Set/Remove style ES_PASSWORD by SetWindowLong(GWL_STYLE) does nothing,
|
|
|
|
send EM_SETPASSWORDCHAR just works. */
|
|
|
|
if (param->flags & FLAG_CR_ECHO)
|
|
|
|
SendMessage(wnd_challenge, EM_SETPASSWORDCHAR, 0, 0);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (RecallUsername(param->c->config_name, username))
|
|
|
|
{
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_EDT_AUTH_USER, username);
|
|
|
|
SetFocus(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS));
|
|
|
|
}
|
|
|
|
if (RecallAuthPass(param->c->config_name, password))
|
|
|
|
{
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_EDT_AUTH_PASS, password);
|
|
|
|
if (username[0] != L'\0' && !(param->flags & FLAG_CR_TYPE_SCRV1)
|
|
|
|
&& password[0] != L'\0' && param->c->failed_auth_attempts == 0)
|
|
|
|
{
|
|
|
|
/* user/pass available and no challenge response needed: skip dialog
|
|
|
|
* if silent_connection is on, else auto submit after a few seconds.
|
|
|
|
* User can interrupt.
|
|
|
|
*/
|
|
|
|
SetFocus(GetDlgItem(hwndDlg, IDOK));
|
|
|
|
UINT timeout = o.silent_connection ? 0 : 6; /* in seconds */
|
|
|
|
AutoCloseSetup(hwndDlg, IDOK, timeout, ID_TXT_WARNING, IDS_NFO_AUTO_CONNECT);
|
|
|
|
}
|
|
|
|
/* if auth failed, highlight password so that user can type over */
|
|
|
|
else if (param->c->failed_auth_attempts)
|
|
|
|
{
|
|
|
|
SendMessage(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), EM_SETSEL, 0, MAKELONG(0,-1));
|
|
|
|
}
|
|
|
|
else if (param->flags & FLAG_CR_TYPE_SCRV1)
|
|
|
|
{
|
|
|
|
SetFocus(GetDlgItem(hwndDlg, ID_EDT_AUTH_CHALLENGE));
|
|
|
|
}
|
|
|
|
SecureZeroMemory(password, sizeof(password));
|
|
|
|
}
|
|
|
|
if (param->c->flags & FLAG_DISABLE_SAVE_PASS)
|
|
|
|
ShowWindow(GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), SW_HIDE);
|
|
|
|
else if (param->c->flags & FLAG_SAVE_AUTH_PASS)
|
|
|
|
Button_SetCheck(GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_CHECKED);
|
|
|
|
|
|
|
|
SetWindowText (hwndDlg, param->c->config_name);
|
|
|
|
if (param->c->failed_auth_attempts > 0)
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_TXT_WARNING, LoadLocalizedString(IDS_NFO_AUTH_PASS_RETRY));
|
|
|
|
|
|
|
|
if (param->c->state == resuming)
|
|
|
|
ForceForegroundWindow(hwndDlg);
|
|
|
|
else
|
|
|
|
SetForegroundWindow(hwndDlg);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_LBUTTONDOWN:
|
|
|
|
case WM_RBUTTONDOWN:
|
|
|
|
case WM_NCLBUTTONDOWN:
|
|
|
|
case WM_NCRBUTTONDOWN:
|
|
|
|
/* user interrupt */
|
|
|
|
AutoCloseCancel(hwndDlg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
switch (LOWORD(wParam))
|
|
|
|
{
|
|
|
|
case ID_EDT_AUTH_USER:
|
|
|
|
case ID_EDT_AUTH_PASS:
|
|
|
|
case ID_EDT_AUTH_CHALLENGE:
|
|
|
|
if (HIWORD(wParam) == EN_UPDATE)
|
|
|
|
{
|
|
|
|
/* enable OK button only if username and either password or response are filled */
|
|
|
|
BOOL enableOK = GetWindowTextLength(GetDlgItem(hwndDlg, ID_EDT_AUTH_USER))
|
|
|
|
&& (GetWindowTextLength(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS))
|
|
|
|
|| ((param->flags & FLAG_CR_TYPE_SCRV1)
|
|
|
|
&& GetWindowTextLength(GetDlgItem(hwndDlg, ID_EDT_AUTH_CHALLENGE)))
|
|
|
|
);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), enableOK);
|
|
|
|
}
|
|
|
|
AutoCloseCancel(hwndDlg); /* user interrupt */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ID_CHK_SAVE_PASS:
|
|
|
|
param->c->flags ^= FLAG_SAVE_AUTH_PASS;
|
|
|
|
if (param->c->flags & FLAG_SAVE_AUTH_PASS)
|
|
|
|
Button_SetCheck(GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_CHECKED);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DeleteSavedAuthPass(param->c->config_name);
|
|
|
|
Button_SetCheck(GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_UNCHECKED);
|
|
|
|
}
|
|
|
|
AutoCloseCancel(hwndDlg); /* user interrupt */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IDOK:
|
|
|
|
if (GetDlgItemTextW(hwndDlg, ID_EDT_AUTH_USER, username, _countof(username)))
|
|
|
|
{
|
|
|
|
if (!validate_input(username, L"\n"))
|
|
|
|
{
|
|
|
|
show_error_tip(GetDlgItem(hwndDlg, ID_EDT_AUTH_USER), LoadLocalizedString(IDS_ERR_INVALID_USERNAME_INPUT));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
SaveUsername(param->c->config_name, username);
|
|
|
|
}
|
|
|
|
if (GetDlgItemTextW(hwndDlg, ID_EDT_AUTH_PASS, password, _countof(password)))
|
|
|
|
{
|
|
|
|
if (!validate_input(password, L"\n"))
|
|
|
|
{
|
|
|
|
show_error_tip(GetDlgItem(hwndDlg, ID_EDT_AUTH_PASS), LoadLocalizedString(IDS_ERR_INVALID_PASSWORD_INPUT));
|
|
|
|
SecureZeroMemory(password, sizeof(password));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if ( param->c->flags & FLAG_SAVE_AUTH_PASS && wcslen(password) )
|
|
|
|
{
|
|
|
|
SaveAuthPass(param->c->config_name, password);
|
|
|
|
}
|
|
|
|
SecureZeroMemory(password, sizeof(password));
|
|
|
|
}
|
|
|
|
ManagementCommandFromInput(param->c, "username \"Auth\" \"%s\"", hwndDlg, ID_EDT_AUTH_USER);
|
|
|
|
if (param->flags & FLAG_CR_TYPE_SCRV1)
|
|
|
|
ManagementCommandFromTwoInputsBase64(param->c, "password \"Auth\" \"SCRV1:%s:%s\"", hwndDlg, ID_EDT_AUTH_PASS, ID_EDT_AUTH_CHALLENGE);
|
|
|
|
else
|
|
|
|
ManagementCommandFromInput(param->c, "password \"Auth\" \"%s\"", hwndDlg, ID_EDT_AUTH_PASS);
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case IDCANCEL:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
StopOpenVPN(param->c);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_CTLCOLORSTATIC:
|
|
|
|
if (GetDlgCtrlID((HWND) lParam) == ID_TXT_WARNING)
|
|
|
|
{
|
|
|
|
autoclose *ac = (autoclose *)GetProp(hwndDlg, L"AutoClose");
|
|
|
|
HBRUSH br = (HBRUSH) DefWindowProc(hwndDlg, msg, wParam, lParam);
|
|
|
|
/* This text id is used for auth failure warning or autoclose message. Use appropriate color */
|
|
|
|
COLORREF clr = o.clr_warning;
|
|
|
|
if (ac && ac->txtid == ID_TXT_WARNING)
|
|
|
|
clr = ac->txtclr;
|
|
|
|
SetTextColor((HDC) wParam, clr);
|
|
|
|
return (INT_PTR) br;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_CLOSE:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
StopOpenVPN(param->c);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_NCDESTROY:
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
free_auth_param (param);
|
|
|
|
AutoCloseCancel(hwndDlg);
|
|
|
|
RemoveProp(hwndDlg, cfgProp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DialogProc for challenge-response, token PIN etc.
|
|
|
|
*/
|
|
|
|
INT_PTR CALLBACK
|
|
|
|
GenericPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
auth_param_t *param;
|
|
|
|
WCHAR password[USER_PASS_LEN];
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case WM_INITDIALOG:
|
|
|
|
param = (auth_param_t *) lParam;
|
|
|
|
TRY_SETPROP(hwndDlg, cfgProp, (HANDLE) param);
|
|
|
|
|
|
|
|
WCHAR *wstr = Widen (param->str);
|
|
|
|
if (!wstr)
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Error converting challenge string to widechar", false);
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (param->flags & FLAG_CR_TYPE_CRV1 || param->flags & FLAG_CR_TYPE_CRTEXT)
|
|
|
|
{
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_TXT_DESCRIPTION, wstr);
|
|
|
|
|
|
|
|
/* Set password echo on if needed */
|
|
|
|
if (param->flags & FLAG_CR_ECHO)
|
|
|
|
SendMessage(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), EM_SETPASSWORDCHAR, 0, 0);
|
|
|
|
|
|
|
|
/* Rendered size of challenge text and window rectangle */
|
|
|
|
SIZE sz = {0};
|
|
|
|
RECT rect = {0};
|
|
|
|
HDC hdc = GetDC(GetDlgItem(hwndDlg, ID_TXT_DESCRIPTION));
|
|
|
|
GetWindowRect(hwndDlg, &rect);
|
|
|
|
rect.right -= rect.left;
|
|
|
|
rect.bottom -= rect.top;
|
|
|
|
|
|
|
|
/* if space for text + some margin exceeds the window size, resize */
|
|
|
|
if (GetTextExtentPoint32W(hdc, wstr, wcslen(wstr), &sz)
|
|
|
|
&& LPtoDP(hdc, (POINT *) &sz, 1) /* logical to device units */
|
|
|
|
&& sz.cx + DPI_SCALE(15) > rect.right) /* 15 nominal pixel margin space */
|
|
|
|
{
|
|
|
|
/* new horizontal dimension with a max of 640 nominal pixels */
|
|
|
|
rect.right = min(DPI_SCALE(640), sz.cx + DPI_SCALE(15));
|
|
|
|
SetWindowPos(hwndDlg, NULL, 0, 0, rect.right, rect.bottom, SWP_NOMOVE);
|
|
|
|
PrintDebug(L"Window resized to = %d %d", rect.right, rect.bottom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (param->flags & FLAG_PASS_TOKEN)
|
|
|
|
{
|
|
|
|
SetWindowText(hwndDlg, LoadLocalizedString(IDS_NFO_TOKEN_PASSWORD_CAPTION));
|
|
|
|
SetDlgItemText(hwndDlg, ID_TXT_DESCRIPTION, LoadLocalizedString(IDS_NFO_TOKEN_PASSWORD_REQUEST, param->id));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Unknown password request", false);
|
|
|
|
SetDlgItemText(hwndDlg, ID_TXT_DESCRIPTION, wstr);
|
|
|
|
}
|
|
|
|
free(wstr);
|
|
|
|
|
|
|
|
AppendTextToCaption (hwndDlg, param->c->config_name);
|
|
|
|
if (param->c->state == resuming)
|
|
|
|
ForceForegroundWindow(hwndDlg);
|
|
|
|
else
|
|
|
|
SetForegroundWindow(hwndDlg);
|
|
|
|
|
|
|
|
/* If response is not required hide the response field */
|
|
|
|
if ((param->flags & FLAG_CR_TYPE_CRV1 || param->flags & FLAG_CR_TYPE_CRTEXT)
|
|
|
|
&& !(param->flags & FLAG_CR_RESPONSE))
|
|
|
|
{
|
|
|
|
ShowWindow(GetDlgItem(hwndDlg, ID_LTEXT_RESPONSE), SW_HIDE);
|
|
|
|
ShowWindow(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), SW_HIDE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* disable OK button until response is filled-in */
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
const char *template;
|
|
|
|
char *fmt;
|
|
|
|
switch (LOWORD(wParam))
|
|
|
|
{
|
|
|
|
case ID_EDT_RESPONSE:
|
|
|
|
if (HIWORD(wParam) == EN_UPDATE)
|
|
|
|
{
|
|
|
|
/* enable OK if response is non-empty */
|
|
|
|
BOOL enableOK = GetWindowTextLength((HWND) lParam);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), enableOK);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IDOK:
|
|
|
|
if (GetDlgItemTextW(hwndDlg, ID_EDT_RESPONSE, password, _countof(password))
|
|
|
|
&& !validate_input(password, L"\n"))
|
|
|
|
{
|
|
|
|
show_error_tip(GetDlgItem(hwndDlg, ID_EDT_RESPONSE), LoadLocalizedString(IDS_ERR_INVALID_PASSWORD_INPUT));
|
|
|
|
SecureZeroMemory(password, sizeof(password));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (param->flags & FLAG_CR_TYPE_CRTEXT)
|
|
|
|
{
|
|
|
|
ManagementCommandFromInputBase64(param->c, "cr-response \"%s\"", hwndDlg, ID_EDT_RESPONSE);
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
if (param->flags & FLAG_CR_TYPE_CRV1)
|
|
|
|
{
|
|
|
|
/* send username */
|
|
|
|
template = "username \"Auth\" \"%s\"";
|
|
|
|
char *username = escape_string(param->user);
|
|
|
|
fmt = malloc(strlen(template) + strlen(username));
|
|
|
|
|
|
|
|
if (fmt && username)
|
|
|
|
{
|
|
|
|
sprintf(fmt, template, username);
|
|
|
|
ManagementCommand(param->c, fmt, NULL, regular);
|
|
|
|
}
|
|
|
|
else /* no memory? send an emty username and let it error out */
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ",
|
|
|
|
L"Out of memory: sending a generic username for dynamic CR", false);
|
|
|
|
ManagementCommand(param->c, "username \"Auth\" \"user\"", NULL, regular);
|
|
|
|
}
|
|
|
|
free(fmt);
|
|
|
|
free(username);
|
|
|
|
|
|
|
|
/* password template */
|
|
|
|
template = "password \"Auth\" \"CRV1::%s::%%s\"";
|
|
|
|
}
|
|
|
|
else /* generic password request of type param->id */
|
|
|
|
{
|
|
|
|
template = "password \"%s\" \"%%s\"";
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt = malloc(strlen(template) + strlen(param->id));
|
|
|
|
if (fmt)
|
|
|
|
{
|
|
|
|
sprintf(fmt, template, param->id);
|
|
|
|
PrintDebug(L"Send passwd to mgmt with format: '%hs'", fmt);
|
|
|
|
ManagementCommandFromInput(param->c, fmt, hwndDlg, ID_EDT_RESPONSE);
|
|
|
|
free (fmt);
|
|
|
|
}
|
|
|
|
else /* no memory? send stop signal */
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ",
|
|
|
|
L"Out of memory in password dialog: sending stop signal", false);
|
|
|
|
StopOpenVPN (param->c);
|
|
|
|
}
|
|
|
|
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case IDCANCEL:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
StopOpenVPN(param->c);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_OVPN_STATE: /* state change message is received from OpenVPN daemon */
|
|
|
|
/*
|
|
|
|
* AUTH_PENDING immediately transitions to GET_CONFIG until
|
|
|
|
* auth succeeds or connection restarts/aborts.
|
|
|
|
* Close the CR_TEXT dialog if state changes to anything
|
|
|
|
* other than GET_CONFIG. The state is in lParam.
|
|
|
|
*/
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
if ((param->flags & FLAG_CR_TYPE_CRTEXT)
|
|
|
|
&& strcmp((const char *) lParam, "GET_CONFIG"))
|
|
|
|
{
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
case WM_CLOSE:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_NCDESTROY:
|
|
|
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
free_auth_param (param);
|
|
|
|
RemoveProp(hwndDlg, cfgProp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DialogProc for OpenVPN private key password dialog windows
|
|
|
|
*/
|
|
|
|
INT_PTR CALLBACK
|
|
|
|
PrivKeyPassDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
connection_t *c;
|
|
|
|
WCHAR passphrase[KEY_PASS_LEN];
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case WM_INITDIALOG:
|
|
|
|
/* Set connection for this dialog and show it */
|
|
|
|
c = (connection_t *) lParam;
|
|
|
|
TRY_SETPROP(hwndDlg, cfgProp, (HANDLE) c);
|
|
|
|
AppendTextToCaption (hwndDlg, c->config_name);
|
|
|
|
if (RecallKeyPass(c->config_name, passphrase) && wcslen(passphrase)
|
|
|
|
&& c->failed_psw_attempts == 0)
|
|
|
|
{
|
|
|
|
/* Use the saved password and skip the dialog */
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_EDT_PASSPHRASE, passphrase);
|
|
|
|
SecureZeroMemory(passphrase, sizeof(passphrase));
|
|
|
|
ManagementCommandFromInput(c, "password \"Private Key\" \"%s\"", hwndDlg, ID_EDT_PASSPHRASE);
|
|
|
|
EndDialog(hwndDlg, IDOK);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
if (c->flags & FLAG_DISABLE_SAVE_PASS)
|
|
|
|
ShowWindow(GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), SW_HIDE);
|
|
|
|
else if (c->flags & FLAG_SAVE_KEY_PASS)
|
|
|
|
Button_SetCheck (GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_CHECKED);
|
|
|
|
if (c->failed_psw_attempts > 0)
|
|
|
|
SetDlgItemTextW(hwndDlg, ID_TXT_WARNING, LoadLocalizedString(IDS_NFO_KEY_PASS_RETRY));
|
|
|
|
if (c->state == resuming)
|
|
|
|
ForceForegroundWindow(hwndDlg);
|
|
|
|
else
|
|
|
|
SetForegroundWindow(hwndDlg);
|
|
|
|
|
|
|
|
/* disable OK button by default - not disabled in resources */
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
switch (LOWORD(wParam))
|
|
|
|
{
|
|
|
|
case ID_CHK_SAVE_PASS:
|
|
|
|
c->flags ^= FLAG_SAVE_KEY_PASS;
|
|
|
|
if (c->flags & FLAG_SAVE_KEY_PASS)
|
|
|
|
Button_SetCheck (GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_CHECKED);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Button_SetCheck (GetDlgItem (hwndDlg, ID_CHK_SAVE_PASS), BST_UNCHECKED);
|
|
|
|
DeleteSavedKeyPass(c->config_name);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ID_EDT_PASSPHRASE:
|
|
|
|
if (HIWORD(wParam) == EN_UPDATE)
|
|
|
|
{
|
|
|
|
/* enable OK if response is non-empty */
|
|
|
|
BOOL enableOK = GetWindowTextLength((HWND) lParam);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), enableOK);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IDOK:
|
|
|
|
if (GetDlgItemTextW(hwndDlg, ID_EDT_PASSPHRASE, passphrase, _countof(passphrase)))
|
|
|
|
{
|
|
|
|
if (!validate_input(passphrase, L"\n"))
|
|
|
|
{
|
|
|
|
show_error_tip(GetDlgItem(hwndDlg, ID_EDT_PASSPHRASE), LoadLocalizedString(IDS_ERR_INVALID_PASSWORD_INPUT));
|
|
|
|
SecureZeroMemory(passphrase, sizeof(passphrase));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if ((c->flags & FLAG_SAVE_KEY_PASS) && wcslen(passphrase) > 0)
|
|
|
|
{
|
|
|
|
SaveKeyPass(c->config_name, passphrase);
|
|
|
|
}
|
|
|
|
SecureZeroMemory(passphrase, sizeof(passphrase));
|
|
|
|
}
|
|
|
|
ManagementCommandFromInput(c, "password \"Private Key\" \"%s\"", hwndDlg, ID_EDT_PASSPHRASE);
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case IDCANCEL:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
StopOpenVPN (c);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_CTLCOLORSTATIC:
|
|
|
|
if (GetDlgCtrlID((HWND) lParam) == ID_TXT_WARNING)
|
|
|
|
{
|
|
|
|
HBRUSH br = (HBRUSH) DefWindowProc(hwndDlg, msg, wParam, lParam);
|
|
|
|
SetTextColor((HDC) wParam, o.clr_warning);
|
|
|
|
return (INT_PTR) br;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_CLOSE:
|
|
|
|
EndDialog(hwndDlg, LOWORD(wParam));
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_NCDESTROY:
|
|
|
|
RemoveProp(hwndDlg, cfgProp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
free_dynamic_cr (connection_t *c)
|
|
|
|
{
|
|
|
|
free (c->dynamic_cr);
|
|
|
|
c->dynamic_cr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse dynamic challenge string received from the server. Returns
|
|
|
|
* true on success. The caller must free param->str and param->id
|
|
|
|
* even on error.
|
|
|
|
*/
|
|
|
|
BOOL
|
|
|
|
parse_dynamic_cr (const char *str, auth_param_t *param)
|
|
|
|
{
|
|
|
|
BOOL ret = FALSE;
|
|
|
|
char *token[4] = {0};
|
|
|
|
char *p = strdup (str);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
char *p1;
|
|
|
|
|
|
|
|
if (!param || !p) goto out;
|
|
|
|
|
|
|
|
/* expected: str = "E,R:challenge_id:user_b64:challenge_str" */
|
|
|
|
const char *delim = ":";
|
|
|
|
for (i = 0, p1 = p; i < 4; ++i, p1 = NULL)
|
|
|
|
{
|
|
|
|
if (i == 3)
|
|
|
|
delim = "" ; /* take the entire trailing string as the challenge */
|
|
|
|
token[i] = strtok (p1, delim); /* strtok is thread-safe on Windows */
|
|
|
|
if (!token[i])
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Error parsing dynamic challenge string", false);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Base64Decode(token[2], ¶m->user) < 0)
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Error decoding the username in dynamic challenge string", false);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
param->flags |= FLAG_CR_TYPE_CRV1;
|
|
|
|
param->flags |= strchr(token[0], 'E') ? FLAG_CR_ECHO : 0;
|
|
|
|
param->flags |= strchr(token[0], 'R') ? FLAG_CR_RESPONSE : 0;
|
|
|
|
param->id = strdup(token[1]);
|
|
|
|
param->str = strdup(token[3]);
|
|
|
|
if (!param->id || !param->str)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = TRUE;
|
|
|
|
|
|
|
|
out:
|
|
|
|
free (p);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse crtext string received from the server. Returns
|
|
|
|
* true on success. The caller must free param->str even on error.
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
parse_crtext (const char* str, auth_param_t* param)
|
|
|
|
{
|
|
|
|
BOOL ret = FALSE;
|
|
|
|
char* token[2] = { 0 };
|
|
|
|
char* p = strdup(str);
|
|
|
|
char* p1;
|
|
|
|
|
|
|
|
if (!param || !p) goto out;
|
|
|
|
|
|
|
|
/* expected: str = "E,R:challenge_str" */
|
|
|
|
token[0] = p;
|
|
|
|
p1 = strchr(p, ':');
|
|
|
|
if (!p1)
|
|
|
|
{
|
|
|
|
WriteStatusLog(param->c, L"GUI> ", L"Error parsing crtext challenge string", false);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
*p1 = '\0';
|
|
|
|
token[1] = p1 + 1;
|
|
|
|
|
|
|
|
param->flags |= FLAG_CR_TYPE_CRTEXT;
|
|
|
|
param->flags |= strchr(token[0], 'E') ? FLAG_CR_ECHO : 0;
|
|
|
|
param->flags |= strchr(token[0], 'R') ? FLAG_CR_RESPONSE : 0;
|
|
|
|
param->str = strdup(token[1]);
|
|
|
|
if (!param->str)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = TRUE;
|
|
|
|
|
|
|
|
out:
|
|
|
|
free(p);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse password or string request of the form "Need 'What' password/string MSG:message"
|
|
|
|
* and assign param->id = What, param->str = message. Also set param->flags if the type
|
|
|
|
* of the requested info is known. If message is empty param->id is copied to param->str.
|
|
|
|
* Return true on succsess. The caller must free param even when the function fails.
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
parse_input_request (const char *msg, auth_param_t *param)
|
|
|
|
{
|
|
|
|
BOOL ret = FALSE;
|
|
|
|
char *p = strdup (msg);
|
|
|
|
char *sep[4] = {" ", "'", " ", ""}; /* separators to use to break up msg */
|
|
|
|
char *token[4];
|
|
|
|
|
|
|
|
char *p1 = p;
|
|
|
|
for (int i = 0; i < 4; ++i, p1 = NULL)
|
|
|
|
{
|
|
|
|
token[i] = strtok (p1, sep[i]); /* strtok is thread-safe on Windows */
|
|
|
|
if (!token[i] && i < 3) /* first three tokens required */
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (token[3] && strncmp(token[3], "MSG:", 4) == 0)
|
|
|
|
token[3] += 4;
|
|
|
|
if (!token[3] || !*token[3]) /* use id as the description if none provided */
|
|
|
|
token[3] = token[1];
|
|
|
|
|
|
|
|
PrintDebug (L"Tokens: '%hs' '%hs' '%hs' '%hs'", token[0], token[1],
|
|
|
|
token[2], token[3]);
|
|
|
|
|
|
|
|
if (strcmp (token[0], "Need") != 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if ((param->id = strdup(token[1])) == NULL)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (strcmp(token[2], "password") == 0)
|
|
|
|
{
|
|
|
|
if (strcmp (param->id, "Private Key") == 0)
|
|
|
|
param->flags |= FLAG_PASS_PKEY;
|
|
|
|
else
|
|
|
|
param->flags |= FLAG_PASS_TOKEN;
|
|
|
|
}
|
|
|
|
else if (strcmp(token[2], "string") == 0
|
|
|
|
&& strcmp (param->id, "pkcs11-id-request") == 0)
|
|
|
|
{
|
|
|
|
param->flags |= FLAG_STRING_PKCS11;
|
|
|
|
}
|
|
|
|
|
|
|
|
param->str = strdup (token[3]);
|
|
|
|
if (param->str == NULL)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
PrintDebug (L"parse_input_request: id = '%hs' str = '%hs' flags = %u",
|
|
|
|
param->id, param->str, param->flags);
|
|
|
|
ret = TRUE;
|
|
|
|
|
|
|
|
out:
|
|
|
|
free (p);
|
|
|
|
if (!ret)
|
|
|
|
PrintDebug (L"Error parsing password/string request msg: <%hs>", msg);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle >ECHO: request from OpenVPN management interface
|
|
|
|
* Expect msg = timestamp,message
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnEcho(connection_t *c, char *msg)
|
|
|
|
{
|
|
|
|
time_t timestamp = strtoul(msg, NULL, 10); /* openvpn prints these as %u */
|
|
|
|
|
|
|
|
PrintDebug(L"OnEcho with msg = %hs", msg);
|
|
|
|
if (!(msg = strchr(msg, ',')))
|
|
|
|
{
|
|
|
|
PrintDebug(L"OnEcho: msg format not recognized");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
msg++;
|
|
|
|
if (strcmp(msg, "forget-passwords") == 0)
|
|
|
|
{
|
|
|
|
DeleteSavedPasswords(c->config_name);
|
|
|
|
}
|
|
|
|
else if (strcmp(msg, "save-passwords") == 0)
|
|
|
|
{
|
|
|
|
if (c->flags & FLAG_DISABLE_SAVE_PASS)
|
|
|
|
WriteStatusLog(c, L"GUI> echo save-passwords: ",
|
|
|
|
L"Ignored as disable_save_passwords is enabled.", false);
|
|
|
|
else
|
|
|
|
c->flags |= (FLAG_SAVE_KEY_PASS | FLAG_SAVE_AUTH_PASS);
|
|
|
|
}
|
|
|
|
else if (strbegins(msg, "setenv "))
|
|
|
|
{
|
|
|
|
process_setenv(c, timestamp, msg);
|
|
|
|
}
|
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
|
|
|
else if (strbegins(msg, "msg"))
|
|
|
|
{
|
|
|
|
echo_msg_process(c, timestamp, msg);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wchar_t errmsg[256];
|
|
|
|
_sntprintf_0(errmsg, L"WARNING: Unknown ECHO directive '%hs' ignored.", msg);
|
|
|
|
WriteStatusLog(c, L"GUI> ", errmsg, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle >PASSWORD: request from OpenVPN management interface
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnPassword(connection_t *c, char *msg)
|
|
|
|
{
|
|
|
|
PrintDebug(L"OnPassword with msg = %hs", msg);
|
|
|
|
if (strncmp(msg, "Verification Failed", 19) == 0)
|
|
|
|
{
|
|
|
|
/* If the failure is due to dynamic challenge save the challenge string */
|
|
|
|
char *chstr = strstr(msg, "CRV1:");
|
|
|
|
|
|
|
|
free_dynamic_cr (c);
|
|
|
|
if (chstr)
|
|
|
|
{
|
|
|
|
chstr += 5; /* beginning of dynamic CR string */
|
|
|
|
|
|
|
|
/* Save the string for later processing during next Auth request */
|
|
|
|
c->dynamic_cr = strdup(chstr);
|
|
|
|
if (c->dynamic_cr && (chstr = strstr (c->dynamic_cr, "']")) != NULL)
|
|
|
|
*chstr = '\0';
|
|
|
|
|
|
|
|
PrintDebug(L"Got dynamic challenge: <%hs>", c->dynamic_cr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strstr(msg, "'Auth'"))
|
|
|
|
{
|
|
|
|
char *chstr;
|
|
|
|
auth_param_t *param = (auth_param_t *) calloc(1, sizeof(auth_param_t));
|
|
|
|
|
|
|
|
if (!param)
|
|
|
|
{
|
|
|
|
WriteStatusLog (c, L"GUI> ", L"Error: Out of memory - ignoring user-auth request", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
param->c = c;
|
|
|
|
|
|
|
|
if (c->dynamic_cr)
|
|
|
|
{
|
|
|
|
if (!parse_dynamic_cr (c->dynamic_cr, param))
|
|
|
|
{
|
|
|
|
WriteStatusLog (c, L"GUI> ", L"Error parsing dynamic challenge string", FALSE);
|
|
|
|
free_dynamic_cr (c);
|
|
|
|
free_auth_param (param);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_CHALLENGE_RESPONSE, GenericPassDialogFunc, (LPARAM) param);
|
|
|
|
free_dynamic_cr (c);
|
|
|
|
}
|
|
|
|
else if ( (chstr = strstr(msg, "SC:")) && strlen (chstr) > 5)
|
|
|
|
{
|
|
|
|
param->flags |= FLAG_CR_TYPE_SCRV1;
|
|
|
|
param->flags |= (*(chstr + 3) != '0') ? FLAG_CR_ECHO : 0;
|
|
|
|
param->str = strdup(chstr + 5);
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_AUTH_CHALLENGE, UserAuthDialogFunc, (LPARAM) param);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_AUTH, UserAuthDialogFunc, (LPARAM) param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strstr(msg, "'Private Key'"))
|
|
|
|
{
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_PASSPHRASE, PrivKeyPassDialogFunc, (LPARAM) c);
|
|
|
|
}
|
|
|
|
else if (strstr(msg, "'HTTP Proxy'"))
|
|
|
|
{
|
|
|
|
QueryProxyAuth(c, http);
|
|
|
|
}
|
|
|
|
else if (strstr(msg, "'SOCKS Proxy'"))
|
|
|
|
{
|
|
|
|
QueryProxyAuth(c, socks);
|
|
|
|
}
|
|
|
|
/* All other password requests such as PKCS11 pin */
|
|
|
|
else if (strncmp(msg, "Need '", 6) == 0)
|
|
|
|
{
|
|
|
|
auth_param_t *param = (auth_param_t *) calloc(1, sizeof(auth_param_t));
|
|
|
|
|
|
|
|
if (!param)
|
|
|
|
{
|
|
|
|
WriteStatusLog (c, L"GUI> ", L"Error: Out of memory - ignoring user-auth request", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
param->c = c;
|
|
|
|
if (!parse_input_request (msg, param))
|
|
|
|
{
|
|
|
|
free_auth_param(param);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_CHALLENGE_RESPONSE, GenericPassDialogFunc, (LPARAM) param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle management connection timeout
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnTimeout(connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
/* Connection to management timed out -- keep trying unless killed
|
|
|
|
* by a startup error from service or from openvpn daemon process.
|
|
|
|
* The user can terminate by pressing disconnect.
|
|
|
|
*/
|
|
|
|
if (o.silent_connection == 0)
|
|
|
|
{
|
|
|
|
SetForegroundWindow(c->hwndStatus);
|
|
|
|
ShowWindow(c->hwndStatus, SW_SHOW);
|
|
|
|
}
|
|
|
|
WriteStatusLog (c, L"GUI> ", LoadLocalizedString(IDS_NFO_CONN_TIMEOUT, c->log_path), false);
|
|
|
|
WriteStatusLog (c, L"GUI> ", L"Retrying. Press disconnect to abort", false);
|
|
|
|
c->state = connecting;
|
|
|
|
if (!OpenManagement(c))
|
|
|
|
{
|
|
|
|
MessageBoxExW(NULL, L"Failed to open management", _T(PACKAGE_NAME),
|
|
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONERROR | MBOX_RTL_FLAGS, GetGUILanguage());
|
|
|
|
StopOpenVPN(c);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle exit of the OpenVPN process
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnStop(connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
UINT txt_id, msg_id;
|
|
|
|
SetMenuStatus(c, disconnected);
|
|
|
|
|
|
|
|
switch (c->state)
|
|
|
|
{
|
|
|
|
case connected:
|
|
|
|
/* OpenVPN process ended unexpectedly */
|
|
|
|
c->failed_psw_attempts = 0;
|
|
|
|
c->failed_auth_attempts = 0;
|
|
|
|
c->state = disconnected;
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_DISCONNECTED));
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_DISCONNECTED);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
|
|
|
|
if (o.silent_connection == 0)
|
|
|
|
{
|
|
|
|
SetForegroundWindow(c->hwndStatus);
|
|
|
|
ShowWindow(c->hwndStatus, SW_SHOW);
|
|
|
|
}
|
|
|
|
MessageBoxExW(c->hwndStatus, LoadLocalizedString(IDS_NFO_CONN_TERMINATED, c->config_file),
|
|
|
|
_T(PACKAGE_NAME), MB_OK | MBOX_RTL_FLAGS, GetGUILanguage());
|
|
|
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case resuming:
|
|
|
|
case connecting:
|
|
|
|
case reconnecting:
|
|
|
|
/* We have failed to (re)connect */
|
|
|
|
txt_id = c->state == reconnecting ? IDS_NFO_STATE_FAILED_RECONN : IDS_NFO_STATE_FAILED;
|
|
|
|
msg_id = c->state == reconnecting ? IDS_NFO_RECONN_FAILED : IDS_NFO_CONN_FAILED;
|
|
|
|
|
|
|
|
c->state = disconnecting;
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
c->state = disconnected;
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_DISCONNECTED);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(txt_id));
|
|
|
|
if (o.silent_connection == 0)
|
|
|
|
{
|
|
|
|
SetForegroundWindow(c->hwndStatus);
|
|
|
|
ShowWindow(c->hwndStatus, SW_SHOW);
|
|
|
|
}
|
|
|
|
MessageBoxExW(c->hwndStatus, LoadLocalizedString(msg_id, c->config_name),
|
|
|
|
_T(PACKAGE_NAME), MB_OK | MBOX_RTL_FLAGS, GetGUILanguage());
|
|
|
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case disconnecting:
|
|
|
|
/* Shutdown was initiated by us */
|
|
|
|
c->failed_psw_attempts = 0;
|
|
|
|
c->failed_auth_attempts = 0;
|
|
|
|
c->state = disconnected;
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
{
|
|
|
|
/* user initiated disconnection -- stay detached and do not auto-reconnect */
|
|
|
|
c->auto_connect = false;
|
|
|
|
}
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_DISCONNECTED));
|
|
|
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case onhold:
|
|
|
|
/* stop triggered while on hold -- possibly the daemon exited. Treat same as detaching */
|
|
|
|
case detaching:
|
|
|
|
c->state = disconnected;
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case suspending:
|
|
|
|
c->state = suspended;
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_SUSPENDED));
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert bytecount c to a human readable string.
|
|
|
|
* Input c converted to a string of the form "nnn.. (xxx.y XiB)"
|
|
|
|
* where X is K, M, G, T, P or E depending on the count. The result
|
|
|
|
* is returned in buf up to max of len - 1 wide characters.
|
|
|
|
* Return value: pointer to buf.
|
|
|
|
*/
|
|
|
|
static wchar_t *
|
|
|
|
format_bytecount(wchar_t *buf, size_t len, unsigned long long c)
|
|
|
|
{
|
|
|
|
const char *suf[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", NULL};
|
|
|
|
const char **s = suf;
|
|
|
|
double x = c;
|
|
|
|
|
|
|
|
if (c <= 1024)
|
|
|
|
{
|
|
|
|
swprintf(buf, len, L"%I64u B", c);
|
|
|
|
buf[len-1] = L'\0';
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
while (x > 1024 && *(s+1))
|
|
|
|
{
|
|
|
|
x /= 1024.0;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
swprintf(buf, len, L"%I64u (%.1f %hs)", c, x, *s);
|
|
|
|
buf[len-1] = L'\0';
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle bytecount report from OpenVPN
|
|
|
|
* Expect bytes-in,bytes-out
|
|
|
|
*/
|
|
|
|
void OnByteCount(connection_t *c, char *msg)
|
|
|
|
{
|
|
|
|
if (!msg || sscanf(msg, "%I64u,%I64u", &c->bytes_in, &c->bytes_out) != 2)
|
|
|
|
return;
|
|
|
|
wchar_t in[32], out[32];
|
|
|
|
format_bytecount(in, _countof(in), c->bytes_in);
|
|
|
|
format_bytecount(out, _countof(out), c->bytes_out);
|
|
|
|
SetDlgItemTextW(c->hwndStatus, ID_TXT_BYTECOUNT,
|
|
|
|
LoadLocalizedString(IDS_NFO_BYTECOUNT, in, out));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle INFOMSG from OpenVPN. At the moment it handles
|
|
|
|
* WEB_AUTH:<flags>:<url>, OPEN_URL:<url> and CR_TEXT:<flags>:<challenge-str> messages
|
|
|
|
* used by two-step authentication.
|
|
|
|
*/
|
|
|
|
void OnInfoMsg(connection_t* c, char* msg)
|
|
|
|
{
|
|
|
|
PrintDebug(L"OnInfoMsg with msg = %hs", msg);
|
|
|
|
|
|
|
|
char *p = NULL;
|
|
|
|
wchar_t *url = NULL;
|
|
|
|
|
|
|
|
if (strbegins(msg, "OPEN_URL:"))
|
|
|
|
{
|
|
|
|
url = Widen(msg + 9);
|
|
|
|
}
|
|
|
|
else if (strbegins(msg, "WEB_AUTH:") && (p = strchr(msg + 9, ':')) != NULL)
|
|
|
|
{
|
|
|
|
if (p != msg + 9)
|
|
|
|
{
|
|
|
|
/* flags not empty: log a message that we do not support any flags */
|
|
|
|
*p = '\0';
|
|
|
|
wchar_t *flags = Widen(msg + 9);
|
|
|
|
if (flags)
|
|
|
|
WriteStatusLog (c, L"GUI> Unsupported flags in WEB_AUTH request ignored: ", flags, false);
|
|
|
|
free(flags);
|
|
|
|
}
|
|
|
|
url = Widen(p+1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url)
|
|
|
|
{
|
|
|
|
if (!open_url(url))
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Error: failed to open url from info msg", false);
|
|
|
|
}
|
|
|
|
free(url);
|
|
|
|
}
|
|
|
|
else if (strbegins(msg, "CR_TEXT:"))
|
|
|
|
{
|
|
|
|
auth_param_t* param = (auth_param_t*)calloc(1, sizeof(auth_param_t));
|
|
|
|
if (!param)
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Error: Out of memory - ignoring CR_TEXT request", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
param->c = c;
|
|
|
|
|
|
|
|
if (!parse_crtext(msg + 8, param))
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Error parsing crtext string", FALSE);
|
|
|
|
|
|
|
|
free_auth_param(param);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LocalizedDialogBoxParam(ID_DLG_CHALLENGE_RESPONSE, GenericPassDialogFunc, (LPARAM)param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Break a long line into shorter segments
|
|
|
|
*/
|
|
|
|
static WCHAR *
|
|
|
|
WrapLine (WCHAR *line)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
WCHAR *next = NULL;
|
|
|
|
int len = 80;
|
|
|
|
|
|
|
|
for (i = 0; *line; i++, ++line)
|
|
|
|
{
|
|
|
|
if ((*line == L'\r') || (*line == L'\n'))
|
|
|
|
*line = L' ';
|
|
|
|
if (next && i > len) break;
|
|
|
|
if (iswspace(*line)) next = line;
|
|
|
|
}
|
|
|
|
if (!*line) next = NULL;
|
|
|
|
if (next)
|
|
|
|
{
|
|
|
|
*next = L'\0';
|
|
|
|
++next;
|
|
|
|
}
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write a line to the status log window and optionally to the log file
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio)
|
|
|
|
{
|
|
|
|
/* this can be called without connection (AS profile import), so do nothing in this case */
|
|
|
|
if (!c) return;
|
|
|
|
|
|
|
|
HWND logWnd = GetDlgItem(c->hwndStatus, ID_EDT_LOG);
|
|
|
|
FILE *log_fd;
|
|
|
|
time_t now;
|
|
|
|
WCHAR datetime[26];
|
|
|
|
|
|
|
|
time (&now);
|
|
|
|
/* TODO: change this to use _wctime_s when mingw supports it */
|
|
|
|
wcsncpy (datetime, _wctime(&now), _countof(datetime));
|
|
|
|
datetime[24] = L' ';
|
|
|
|
|
|
|
|
/* change text color if Warning or Error */
|
|
|
|
COLORREF text_clr = 0;
|
|
|
|
|
|
|
|
if (wcsstr(prefix, L"ERROR"))
|
|
|
|
{
|
|
|
|
text_clr = o.clr_error;
|
|
|
|
}
|
|
|
|
else if (wcsstr(prefix, L"WARNING"))
|
|
|
|
{
|
|
|
|
text_clr = o.clr_warning;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (text_clr != 0)
|
|
|
|
{
|
|
|
|
CHARFORMAT cfm = { .cbSize = sizeof(CHARFORMAT),
|
|
|
|
.dwMask = CFM_COLOR|CFM_BOLD,
|
|
|
|
.dwEffects = 0,
|
|
|
|
.crTextColor = text_clr,
|
|
|
|
};
|
|
|
|
SendMessage(logWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cfm);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Remove lines from log window if it is getting full */
|
|
|
|
if (SendMessage(logWnd, EM_GETLINECOUNT, 0, 0) > MAX_LOG_LINES)
|
|
|
|
{
|
|
|
|
int pos = SendMessage(logWnd, EM_LINEINDEX, DEL_LOG_LINES, 0);
|
|
|
|
SendMessage(logWnd, EM_SETSEL, 0, pos);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) _T(""));
|
|
|
|
}
|
|
|
|
/* Append line to log window */
|
|
|
|
SendMessage(logWnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) datetime);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) prefix);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) line);
|
|
|
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) L"\n");
|
|
|
|
|
|
|
|
if (!fileio) return;
|
|
|
|
|
|
|
|
log_fd = _tfopen (c->log_path, TEXT("at+,ccs=UTF-8"));
|
|
|
|
if (log_fd)
|
|
|
|
{
|
|
|
|
fwprintf (log_fd, L"%ls%ls%ls\n", datetime, prefix, line);
|
|
|
|
fclose (log_fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define IO_TIMEOUT 5000 /* milliseconds */
|
|
|
|
|
|
|
|
static void
|
|
|
|
CloseServiceIO (service_io_t *s)
|
|
|
|
{
|
|
|
|
if (s->hEvent)
|
|
|
|
CloseHandle(s->hEvent);
|
|
|
|
s->hEvent = NULL;
|
|
|
|
if (s->pipe && s->pipe != INVALID_HANDLE_VALUE)
|
|
|
|
CloseHandle(s->pipe);
|
|
|
|
s->pipe = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open the service pipe and initialize service I/O.
|
|
|
|
* Failure is not fatal.
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
InitServiceIO (service_io_t *s)
|
|
|
|
{
|
|
|
|
DWORD dwMode = o.ovpn_engine == OPENVPN_ENGINE_OVPN3 ? PIPE_READMODE_BYTE : PIPE_READMODE_MESSAGE;
|
|
|
|
|
|
|
|
CLEAR(*s);
|
|
|
|
|
|
|
|
/* auto-reset event used for signalling i/o completion*/
|
|
|
|
s->hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
|
|
if (!s->hEvent)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->pipe = CreateFile (o.ovpn_engine == OPENVPN_ENGINE_OVPN3 ?
|
|
|
|
OPENVPN_SERVICE_PIPE_NAME_OVPN3 : OPENVPN_SERVICE_PIPE_NAME_OVPN2,
|
|
|
|
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
|
|
|
|
|
|
|
if ( !s->pipe ||
|
|
|
|
s->pipe == INVALID_HANDLE_VALUE ||
|
|
|
|
!SetNamedPipeHandleState(s->pipe, &dwMode, NULL, NULL)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
CloseServiceIO (s);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read-completion routine for interactive service pipe. Call with
|
|
|
|
* err = 0, bytes = 0 to queue a new read request.
|
|
|
|
*/
|
|
|
|
static void WINAPI
|
|
|
|
HandleServiceIO (DWORD err, DWORD bytes, LPOVERLAPPED lpo)
|
|
|
|
{
|
|
|
|
service_io_t *s = (service_io_t *) lpo;
|
|
|
|
int len, capacity;
|
|
|
|
|
|
|
|
len = _countof(s->readbuf);
|
|
|
|
capacity = (len-1)*sizeof(*(s->readbuf));
|
|
|
|
|
|
|
|
if (bytes > 0)
|
|
|
|
{
|
|
|
|
/* messages from the service are not nul terminated */
|
|
|
|
int nchars = bytes/sizeof(s->readbuf[0]);
|
|
|
|
s->readbuf[nchars] = L'\0';
|
|
|
|
SetEvent (s->hEvent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (err)
|
|
|
|
{
|
|
|
|
_snwprintf(s->readbuf, len, L"0x%08x\nInteractive Service disconnected\n", err);
|
|
|
|
s->readbuf[len-1] = L'\0';
|
|
|
|
SetEvent (s->hEvent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise queue next read request */
|
|
|
|
ReadFileEx (s->pipe, s->readbuf, capacity, lpo, HandleServiceIO);
|
|
|
|
/* Any error in the above call will get checked in next round */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write size bytes in buf to the pipe with a timeout.
|
|
|
|
* Retun value: TRUE on success FLASE on error
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
WritePipe (HANDLE pipe, LPVOID buf, DWORD size)
|
|
|
|
{
|
|
|
|
OVERLAPPED o;
|
|
|
|
BOOL retval = FALSE;
|
|
|
|
|
|
|
|
CLEAR(o);
|
|
|
|
o.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|
|
|
|
|
|
|
if (!o.hEvent)
|
|
|
|
{
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WriteFile (pipe, buf, size, NULL, &o) ||
|
|
|
|
GetLastError() == ERROR_IO_PENDING )
|
|
|
|
{
|
|
|
|
if (WaitForSingleObject(o.hEvent, IO_TIMEOUT) == WAIT_OBJECT_0)
|
|
|
|
retval = TRUE;
|
|
|
|
else
|
|
|
|
CancelIo (pipe);
|
|
|
|
// TODO report error -- timeout
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(o.hEvent);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when read from service pipe signals
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
OnService(connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
DWORD err = 0;
|
|
|
|
DWORD pid = 0;
|
|
|
|
WCHAR *p, *buf, *next;
|
|
|
|
const WCHAR *prefix = L"IService> ";
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Duplicate the read buffer and queue the next read request
|
|
|
|
* by calling HandleServiceIO with err = 0, bytes = 0.
|
|
|
|
*/
|
|
|
|
buf = wcsdup(c->iserv.readbuf);
|
|
|
|
HandleServiceIO(0, 0, (LPOVERLAPPED) &c->iserv);
|
|
|
|
|
|
|
|
if (buf == NULL) return;
|
|
|
|
|
|
|
|
/* messages from the service are in the format "0x08x\n%s\n%s" */
|
|
|
|
if (swscanf (buf, L"0x%08x\n", &err) != 1)
|
|
|
|
{
|
|
|
|
free (buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = buf + 11;
|
|
|
|
/* next line is the pid if followed by "\nProcess ID" */
|
|
|
|
if (!err && wcsstr(p, L"\nProcess ID") && swscanf (p, L"0x%08x", &pid) == 1 && pid != 0)
|
|
|
|
{
|
|
|
|
PrintDebug (L"Process ID of openvpn started by IService: %d", pid);
|
|
|
|
c->hProcess = OpenProcess (PROCESS_TERMINATE|PROCESS_QUERY_INFORMATION, FALSE, pid);
|
|
|
|
if (!c->hProcess)
|
|
|
|
PrintDebug (L"Failed to get process handle from pid of openvpn: error = %lu",
|
|
|
|
GetLastError());
|
|
|
|
free (buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (iswspace(*p)) ++p;
|
|
|
|
|
|
|
|
while (p && *p)
|
|
|
|
{
|
|
|
|
next = WrapLine (p);
|
|
|
|
WriteStatusLog (c, prefix, p, false);
|
|
|
|
p = next;
|
|
|
|
}
|
|
|
|
free (buf);
|
|
|
|
|
|
|
|
/* Error from iservice before management interface is connected */
|
|
|
|
switch (err)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case ERROR_STARTUP_DATA:
|
|
|
|
WriteStatusLog (c, prefix, L"OpenVPN not started due to previous errors", true);
|
|
|
|
OnStop (c, NULL);
|
|
|
|
break;
|
|
|
|
case ERROR_OPENVPN_STARTUP:
|
|
|
|
WriteStatusLog (c, prefix, L"Check the log file for details", false);
|
|
|
|
OnStop(c, NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
OnStop(c, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when the directly started openvpn process exits
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
OnProcess (connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
DWORD err;
|
|
|
|
WCHAR tmp[256];
|
|
|
|
|
|
|
|
if (!GetExitCodeProcess(c->hProcess, &err) || err == STILL_ACTIVE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_snwprintf(tmp, _countof(tmp), L"OpenVPN terminated with exit code %lu. "
|
|
|
|
L"See the log file for details", err);
|
|
|
|
tmp[_countof(tmp)-1] = L'\0';
|
|
|
|
WriteStatusLog(c, L"OpenVPN GUI> ", tmp, false);
|
|
|
|
|
|
|
|
OnStop (c, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when NEED-OK is received
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnNeedOk (connection_t *c, char *msg)
|
|
|
|
{
|
|
|
|
char *resp = NULL;
|
|
|
|
WCHAR *wstr = NULL;
|
|
|
|
auth_param_t *param = (auth_param_t *) calloc(1, sizeof(auth_param_t));
|
|
|
|
|
|
|
|
if (!param)
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Error: out of memory while processing NEED-OK. Sending stop signal", false);
|
|
|
|
StopOpenVPN(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!parse_input_request(msg, param))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* allocate space for response : "needok param->id cancel/ok" */
|
|
|
|
resp = malloc (strlen(param->id) + strlen("needok \' \' cancel"));
|
|
|
|
wstr = Widen(param->str);
|
|
|
|
|
|
|
|
if (!wstr || !resp)
|
|
|
|
{
|
|
|
|
WriteStatusLog(c, L"GUI> ", L"Error: out of memory while processing NEED-OK. Sending stop signal", false);
|
|
|
|
StopOpenVPN(c);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *fmt;
|
|
|
|
if (MessageBoxExW(NULL, wstr, L""PACKAGE_NAME, MB_OKCANCEL | MBOX_RTL_FLAGS, GetGUILanguage()) == IDOK)
|
|
|
|
{
|
|
|
|
fmt = "needok \'%s\' ok";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ManagementCommand (c, "auth-retry none", NULL, regular);
|
|
|
|
fmt = "needok \'%s\' cancel";
|
|
|
|
}
|
|
|
|
|
|
|
|
sprintf (resp, fmt, param->id);
|
|
|
|
ManagementCommand (c, resp, NULL, regular);
|
|
|
|
|
|
|
|
out:
|
|
|
|
free_auth_param (param);
|
|
|
|
free(wstr);
|
|
|
|
free(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when NEED-STR is received
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
OnNeedStr (connection_t *c, UNUSED char *msg)
|
|
|
|
{
|
|
|
|
if (strstr(msg, "Need 'pkcs11-id-request'"))
|
|
|
|
{
|
|
|
|
msg = strstr(msg, "MSG:");
|
|
|
|
OnPkcs11(c, msg ? msg + 4 : "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
WriteStatusLog (c, L"GUI> ", L"Error: Received NEED-STR message -- not implemented", false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stop the connection -- this sets the daemon to exit if
|
|
|
|
* started by us, else instructs the daemon to disconnect and
|
|
|
|
* and wait.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
DisconnectDaemon(connection_t *c)
|
|
|
|
{
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
{
|
|
|
|
if (c->manage.connected > 1) /* connected and ready for input */
|
|
|
|
{
|
|
|
|
ManagementCommand(c, "forget-passwords", NULL, regular);
|
|
|
|
ManagementCommand(c, "hold on", NULL, regular);
|
|
|
|
ManagementCommand(c, "signal SIGHUP", NULL, regular);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
OnStop(c, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetEvent(c->exit_event);
|
|
|
|
SetTimer(c->hwndStatus, IDT_STOP_TIMER, 15000, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close open handles
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
Cleanup (connection_t *c)
|
|
|
|
{
|
|
|
|
CloseManagement (c);
|
|
|
|
|
|
|
|
free_dynamic_cr (c);
|
|
|
|
env_item_del_all(c->es);
|
|
|
|
c->es = NULL;
|
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_clear(c, true); /* clear history */
|
|
|
|
pkcs11_list_clear(&c->pkcs11_list);
|
|
|
|
|
|
|
|
if (c->hProcess)
|
|
|
|
CloseHandle (c->hProcess);
|
|
|
|
c->hProcess = NULL;
|
|
|
|
|
|
|
|
if (c->iserv.hEvent)
|
|
|
|
CloseServiceIO (&c->iserv);
|
|
|
|
|
|
|
|
if (c->exit_event)
|
|
|
|
CloseHandle (c->exit_event);
|
|
|
|
c->exit_event = NULL;
|
|
|
|
c->daemon_state[0] = '\0';
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Helper to position and scale widgets in status window using current dpi
|
|
|
|
* Takes status window width and height in screen pixels as input
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
RenderStatusWindow(HWND hwndDlg, UINT w, UINT h)
|
|
|
|
{
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_EDT_LOG), DPI_SCALE(20), DPI_SCALE(25), w - DPI_SCALE(40), h - DPI_SCALE(110), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_TXT_STATUS), DPI_SCALE(20), DPI_SCALE(5), w-DPI_SCALE(30), DPI_SCALE(15), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_TXT_IP), DPI_SCALE(20), h - DPI_SCALE(75), w-DPI_SCALE(30), DPI_SCALE(15), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_TXT_BYTECOUNT), DPI_SCALE(20), h - DPI_SCALE(55), w-DPI_SCALE(210), DPI_SCALE(15), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_TXT_VERSION), w-DPI_SCALE(180), h - DPI_SCALE(55), DPI_SCALE(170), DPI_SCALE(15), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_DISCONNECT), DPI_SCALE(20), h - DPI_SCALE(30), DPI_SCALE(110), DPI_SCALE(23), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_RESTART), DPI_SCALE(145), h - DPI_SCALE(30), DPI_SCALE(110), DPI_SCALE(23), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_DETACH), DPI_SCALE(270), h - DPI_SCALE(30), DPI_SCALE(110), DPI_SCALE(23), TRUE);
|
|
|
|
MoveWindow(GetDlgItem(hwndDlg, ID_HIDE), w - DPI_SCALE(130), h - DPI_SCALE(30), DPI_SCALE(110), DPI_SCALE(23), TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DialogProc for OpenVPN status dialog windows
|
|
|
|
*/
|
|
|
|
INT_PTR CALLBACK
|
|
|
|
StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
connection_t *c;
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case WM_MANAGEMENT:
|
|
|
|
/* Management interface related event */
|
|
|
|
OnManagement(wParam, lParam);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_INITDIALOG:
|
|
|
|
c = (connection_t *) lParam;
|
|
|
|
|
|
|
|
/* Set window icon "disconnected" */
|
|
|
|
SetStatusWinIcon(hwndDlg, ID_ICO_CONNECTING);
|
|
|
|
|
|
|
|
/* Set connection for this dialog */
|
|
|
|
if (!SetPropW(hwndDlg, cfgProp, (HANDLE) c))
|
|
|
|
{
|
|
|
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"%hs:%d SetProp failed (error = 0x%08x)",
|
|
|
|
__func__, __LINE__, GetLastError());
|
|
|
|
DisconnectDaemon(c);
|
|
|
|
DestroyWindow(hwndDlg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create log window */
|
|
|
|
HWND hLogWnd = CreateWindowEx(0, RICHEDIT_CLASS, NULL,
|
|
|
|
WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_SUNKEN|ES_LEFT|
|
|
|
|
ES_MULTILINE|ES_READONLY|ES_AUTOHSCROLL|ES_AUTOVSCROLL,
|
|
|
|
20, 25, 350, 160, hwndDlg, (HMENU) ID_EDT_LOG, o.hInstance, NULL);
|
|
|
|
if (!hLogWnd)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_EDIT_LOGWINDOW);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set font and fontsize of the log window */
|
|
|
|
CHARFORMAT cfm = {
|
|
|
|
.cbSize = sizeof(CHARFORMAT),
|
|
|
|
.dwMask = CFM_SIZE|CFM_FACE|CFM_BOLD,
|
|
|
|
.szFaceName = _T("Microsoft Sans Serif"),
|
|
|
|
.dwEffects = 0,
|
|
|
|
.yHeight = 160
|
|
|
|
};
|
|
|
|
if (SendMessage(hLogWnd, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM) &cfm) == 0)
|
|
|
|
ShowLocalizedMsg(IDS_ERR_SET_SIZE);
|
|
|
|
|
|
|
|
/* display version string as "OpenVPN GUI gui_version/core_version" */
|
|
|
|
wchar_t version[256];
|
|
|
|
_sntprintf_0(version, L"%hs %hs/%hs", PACKAGE_NAME, PACKAGE_VERSION_RESOURCE_STR, o.ovpn_version)
|
|
|
|
SetDlgItemText(hwndDlg, ID_TXT_VERSION, version);
|
|
|
|
|
|
|
|
/* Set size and position of controls */
|
|
|
|
RECT rect;
|
|
|
|
GetWindowRect(hwndDlg, &rect);
|
|
|
|
/* Move the window by upto 100 random pixels to avoid all
|
|
|
|
* status windows fall on top of each other.
|
|
|
|
*/
|
|
|
|
SetWindowPos(hwndDlg, HWND_TOP, rect.left + rand()%100,
|
|
|
|
rect.top + rand()%100, 0, 0, SWP_NOSIZE);
|
|
|
|
GetClientRect(hwndDlg, &rect);
|
|
|
|
RenderStatusWindow(hwndDlg, rect.right, rect.bottom);
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT && o.enable_persistent > 0)
|
|
|
|
{
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, ID_DETACH), TRUE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ShowWindow(GetDlgItem(hwndDlg, ID_DETACH), SW_HIDE);
|
|
|
|
}
|
|
|
|
/* Set focus on the LogWindow so it scrolls automatically */
|
|
|
|
SetFocus(hLogWnd);
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case WM_DPICHANGED:
|
|
|
|
DpiSetScale(&o, HIWORD(wParam));
|
|
|
|
RECT dlgRect;
|
|
|
|
GetClientRect(hwndDlg, &dlgRect);
|
|
|
|
RenderStatusWindow(hwndDlg, dlgRect.right, dlgRect.bottom);
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case WM_SIZE:
|
|
|
|
RenderStatusWindow(hwndDlg, LOWORD(lParam), HIWORD(lParam));
|
|
|
|
InvalidateRect(hwndDlg, NULL, TRUE);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
switch (LOWORD(wParam))
|
|
|
|
{
|
|
|
|
case ID_DISCONNECT:
|
|
|
|
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG));
|
|
|
|
StopOpenVPN(c);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case ID_HIDE:
|
|
|
|
if (c->state != disconnected)
|
|
|
|
ShowWindow(hwndDlg, SW_HIDE);
|
|
|
|
else
|
|
|
|
DestroyWindow(hwndDlg);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case ID_RESTART:
|
|
|
|
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG));
|
|
|
|
RestartOpenVPN(c);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case ID_DETACH:
|
|
|
|
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG));
|
|
|
|
DetachOpenVPN(c);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_SHOWWINDOW:
|
|
|
|
if (wParam == TRUE)
|
|
|
|
{
|
|
|
|
SetFocus(GetDlgItem(hwndDlg, ID_EDT_LOG));
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case WM_CLOSE:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
if (c->state != disconnected && c->state != detached)
|
|
|
|
ShowWindow(hwndDlg, SW_HIDE);
|
|
|
|
else
|
|
|
|
DestroyWindow(hwndDlg);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_NCDESTROY:
|
|
|
|
RemoveProp(hwndDlg, cfgProp);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_DESTROY:
|
|
|
|
PostQuitMessage(0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_OVPN_RELEASE:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
c->state = reconnecting;
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING));
|
|
|
|
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
|
|
|
|
OnHold(c, "");
|
|
|
|
break;
|
|
|
|
|
|
|
|
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))
|
|
|
|
|| c->state == onhold)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
c->state = disconnecting;
|
|
|
|
if (!(c->flags & FLAG_DAEMON_PERSISTENT))
|
|
|
|
{
|
|
|
|
RunDisconnectScript(c, false);
|
|
|
|
}
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
|
|
|
|
SetMenuStatus(c, disconnecting);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM));
|
|
|
|
DisconnectDaemon(c);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_OVPN_DETACH:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
/* just stop the thread keeping openvpn.exe running */
|
|
|
|
c->state = detaching;
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
|
|
|
|
OnStop(c, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_OVPN_SUSPEND:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
c->state = suspending;
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
|
|
|
|
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
|
|
|
|
SetMenuStatus(c, disconnecting);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM));
|
|
|
|
SetEvent(c->exit_event);
|
|
|
|
SetTimer(hwndDlg, IDT_STOP_TIMER, 15000, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_TIMER:
|
|
|
|
PrintDebug(L"WM_TIMER message with wParam = %lu", wParam);
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
if (wParam == IDT_STOP_TIMER)
|
|
|
|
{
|
|
|
|
/* openvpn failed to respond to stop signal -- terminate */
|
|
|
|
TerminateOpenVPN(c);
|
|
|
|
KillTimer (hwndDlg, IDT_STOP_TIMER);
|
|
|
|
OnStop(c, NULL);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_OVPN_RESTART:
|
|
|
|
c = (connection_t *) GetProp(hwndDlg, cfgProp);
|
|
|
|
/* external messages can trigger when we are not ready -- check the state */
|
|
|
|
if (IsWindowEnabled(GetDlgItem(c->hwndStatus, ID_RESTART)))
|
|
|
|
{
|
|
|
|
c->state = reconnecting;
|
|
|
|
ManagementCommand(c, "signal SIGHUP", NULL, regular);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING));
|
|
|
|
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
|
|
|
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
|
|
|
|
}
|
|
|
|
if (!o.silent_connection)
|
|
|
|
{
|
|
|
|
SetForegroundWindow(c->hwndStatus);
|
|
|
|
ShowWindow(c->hwndStatus, SW_SHOW);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ThreadProc for OpenVPN status dialog windows
|
|
|
|
*/
|
|
|
|
static DWORD WINAPI
|
|
|
|
ThreadOpenVPNStatus(void *p)
|
|
|
|
{
|
|
|
|
connection_t *c = p;
|
|
|
|
TCHAR conn_name[200];
|
|
|
|
MSG msg;
|
|
|
|
HANDLE wait_event;
|
|
|
|
|
|
|
|
CLEAR (msg);
|
|
|
|
srand(c->threadId);
|
|
|
|
|
|
|
|
/* Cut of extention from config filename. */
|
|
|
|
_tcsncpy(conn_name, c->config_file, _countof(conn_name));
|
|
|
|
conn_name[_tcslen(conn_name) - _tcslen(o.ext_string) - 1] = _T('\0');
|
|
|
|
|
|
|
|
/* Create and Show Status Dialog */
|
|
|
|
c->hwndStatus = CreateLocalizedDialogParam(ID_DLG_STATUS, StatusDialogFunc, (LPARAM) c);
|
|
|
|
if (!c->hwndStatus)
|
|
|
|
{
|
|
|
|
/* kill daemon process if we started it */
|
|
|
|
SetEvent(c->exit_event);
|
|
|
|
Cleanup(c);
|
|
|
|
c->state = disconnected;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
CheckAndSetTrayIcon();
|
|
|
|
SetMenuStatus(c, connecting);
|
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_CONNECTING));
|
|
|
|
SetWindowText(c->hwndStatus, LoadLocalizedString(IDS_NFO_CONNECTION_XXX, conn_name));
|
|
|
|
|
|
|
|
if (!OpenManagement(c))
|
|
|
|
{
|
|
|
|
MessageBoxExW(NULL, L"Failed to open management", _T(PACKAGE_NAME),
|
|
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONERROR | MBOX_RTL_FLAGS, GetGUILanguage());
|
|
|
|
StopOpenVPN(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start the async read loop for service and set it as the wait event */
|
|
|
|
if (c->iserv.hEvent)
|
|
|
|
{
|
|
|
|
HandleServiceIO (0, 0, (LPOVERLAPPED) &c->iserv);
|
|
|
|
wait_event = c->iserv.hEvent;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wait_event = c->hProcess;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For persistent connections, popup the status win only if we're connecting manually */
|
|
|
|
BOOL show_status_win = (o.silent_connection == 0);
|
|
|
|
if ((c->flags & FLAG_DAEMON_PERSISTENT) && c->state == resuming)
|
|
|
|
{
|
|
|
|
show_status_win = false;
|
|
|
|
}
|
|
|
|
ShowWindow(c->hwndStatus, show_status_win ? SW_SHOW : SW_HIDE);
|
|
|
|
|
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
|
|
|
/* Load echo msg histroy from registry */
|
|
|
|
echo_msg_load(c);
|
|
|
|
|
|
|
|
/* Run the message loop for the status window */
|
|
|
|
while (WM_QUIT != msg.message)
|
|
|
|
{
|
|
|
|
DWORD res;
|
|
|
|
if (wait_event == NULL) /* for persistent connections there is no wait_event */
|
|
|
|
{
|
|
|
|
res = GetMessage(&msg, NULL, 0, 0);
|
|
|
|
if (res == (DWORD) -1) /* log the error and continue */
|
|
|
|
{
|
|
|
|
MsgToEventLog(EVENTLOG_WARNING_TYPE, L"GetMessage for <%ls> returned error (status=%lu)",
|
|
|
|
c->config_name, GetLastError());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (res == 0) /* WM_QUIT */
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
|
|
{
|
|
|
|
if ((res = MsgWaitForMultipleObjectsEx (1, &wait_event, INFINITE, QS_ALLINPUT,
|
|
|
|
MWMO_ALERTABLE)) == WAIT_OBJECT_0)
|
|
|
|
{
|
|
|
|
if (wait_event == c->hProcess)
|
|
|
|
OnProcess (c, NULL);
|
|
|
|
else if (wait_event == c->iserv.hEvent)
|
|
|
|
OnService (c, NULL);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsDialogMessage(c->hwndStatus, &msg) == 0)
|
|
|
|
{
|
|
|
|
TranslateMessage(&msg);
|
|
|
|
DispatchMessage(&msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* release handles etc.*/
|
|
|
|
Cleanup (c);
|
|
|
|
c->hwndStatus = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set priority based on the registry or cmd-line value
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
SetProcessPriority(DWORD *priority)
|
|
|
|
{
|
|
|
|
*priority = NORMAL_PRIORITY_CLASS;
|
|
|
|
if (!_tcscmp(o.priority_string, _T("IDLE_PRIORITY_CLASS")))
|
|
|
|
*priority = IDLE_PRIORITY_CLASS;
|
|
|
|
else if (!_tcscmp(o.priority_string, _T("BELOW_NORMAL_PRIORITY_CLASS")))
|
|
|
|
*priority = BELOW_NORMAL_PRIORITY_CLASS;
|
|
|
|
else if (!_tcscmp(o.priority_string, _T("NORMAL_PRIORITY_CLASS")))
|
|
|
|
*priority = NORMAL_PRIORITY_CLASS;
|
|
|
|
else if (!_tcscmp(o.priority_string, _T("ABOVE_NORMAL_PRIORITY_CLASS")))
|
|
|
|
*priority = ABOVE_NORMAL_PRIORITY_CLASS;
|
|
|
|
else if (!_tcscmp(o.priority_string, _T("HIGH_PRIORITY_CLASS")))
|
|
|
|
*priority = HIGH_PRIORITY_CLASS;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_UNKNOWN_PRIORITY, o.priority_string);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef ENABLE_OVPN3
|
|
|
|
inline static
|
|
|
|
struct json_object* json_object_new_utf16_string(wchar_t *utf16)
|
|
|
|
{
|
|
|
|
DWORD input_size = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, NULL, 0, NULL, NULL);
|
|
|
|
struct json_object* jobj = NULL;
|
|
|
|
char *utf8 = malloc(input_size);
|
|
|
|
if (!utf8)
|
|
|
|
{
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, utf16, -1, utf8, input_size, NULL, NULL);
|
|
|
|
|
|
|
|
jobj = json_object_new_string(utf8);
|
|
|
|
free(utf8);
|
|
|
|
|
|
|
|
out:
|
|
|
|
return jobj;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* PrepareStartJsonRequest(connection_t *c, wchar_t *exit_event_name)
|
|
|
|
{
|
|
|
|
const char *request_header = "POST /start HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: %zd\r\n\r\n";
|
|
|
|
json_object *jobj = json_object_new_object();
|
|
|
|
|
|
|
|
json_object_object_add(jobj, "config_file", json_object_new_utf16_string(c->config_file));
|
|
|
|
json_object_object_add(jobj, "config_dir", json_object_new_utf16_string(c->config_dir));
|
|
|
|
json_object_object_add(jobj, "exit_event_name", json_object_new_utf16_string(exit_event_name));
|
|
|
|
json_object_object_add(jobj, "management_password", json_object_new_string(c->manage.password));
|
|
|
|
json_object_object_add(jobj, "management_host", json_object_new_string(inet_ntoa(c->manage.skaddr.sin_addr)));
|
|
|
|
json_object_object_add(jobj, "management_port", json_object_new_int(ntohs(c->manage.skaddr.sin_port)));
|
|
|
|
json_object_object_add(jobj, "log", json_object_new_utf16_string(c->log_path));
|
|
|
|
json_object_object_add(jobj, "log-append", json_object_new_int(o.log_append));
|
|
|
|
|
|
|
|
const char *body = json_object_to_json_string(jobj);
|
|
|
|
|
|
|
|
int len = snprintf(NULL, 0, request_header, strlen(body)) + strlen(body) + 1;
|
|
|
|
char* request = calloc(1, len);
|
|
|
|
if (request == NULL)
|
|
|
|
{
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
sprintf_s(request, len, request_header, strlen(body));
|
|
|
|
strcat_s(request, len, body);
|
|
|
|
|
|
|
|
out:
|
|
|
|
json_object_put(jobj);
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* If state is on hold -- release */
|
|
|
|
void
|
|
|
|
ReleaseOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
if (c->state != onhold)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PostMessage(c->hwndStatus, WM_OVPN_RELEASE, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start a thread to monitor a connection and launch openvpn.exe if required */
|
|
|
|
BOOL
|
|
|
|
StartOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
CLEAR(c->ip);
|
|
|
|
|
|
|
|
if (c->hwndStatus)
|
|
|
|
{
|
|
|
|
if (c->state == onhold)
|
|
|
|
{
|
|
|
|
ReleaseOpenVPN(c);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
else if (c->state != disconnected && c->state != detached)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PrintDebug(L"Starting openvpn on config %ls", c->config_name);
|
|
|
|
|
|
|
|
/* Create thread to show the connection's status dialog */
|
|
|
|
HANDLE hThread = CreateThread(NULL, 0, ThreadOpenVPNStatus, c, CREATE_SUSPENDED, &c->threadId);
|
|
|
|
if (hThread == NULL)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_THREAD_STATUS);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
{
|
|
|
|
if (!ParseManagementAddress(c))
|
|
|
|
{
|
|
|
|
/* Show an error if manually connecting */
|
|
|
|
if (c->state == disconnected)
|
|
|
|
ShowLocalizedMsgEx(MB_OK|MB_ICONERROR, o.hWnd, TEXT(PACKAGE_NAME), IDS_ERR_PARSE_MGMT_OPTION,
|
|
|
|
c->config_dir, c->config_file);
|
|
|
|
else
|
|
|
|
c->state = disconnected;
|
|
|
|
TerminateThread(hThread, 1);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Launch openvpn.exe using the service or directly */
|
|
|
|
else if (!LaunchOpenVPN(c))
|
|
|
|
{
|
|
|
|
TerminateThread(hThread, 1);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->state = (c->state == suspended || c->state == detached) ? resuming : connecting;
|
|
|
|
|
|
|
|
/* Start the status dialog thread */
|
|
|
|
ResumeThread(hThread);
|
|
|
|
|
|
|
|
if (hThread && hThread != INVALID_HANDLE_VALUE)
|
|
|
|
CloseHandle(hThread);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Launch an OpenVPN process
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
LaunchOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
TCHAR cmdline[1024];
|
|
|
|
TCHAR *options = cmdline + 8;
|
|
|
|
TCHAR exit_event_name[17];
|
|
|
|
HANDLE hStdInRead = NULL, hStdInWrite = NULL;
|
|
|
|
HANDLE hNul = NULL;
|
|
|
|
DWORD written;
|
|
|
|
BOOL retval = FALSE;
|
|
|
|
DWORD passwd_len = 16; /* incuding NUL */
|
|
|
|
|
|
|
|
if (passwd_len > sizeof(c->manage.password))
|
|
|
|
{
|
|
|
|
passwd_len = sizeof(c->manage.password);
|
|
|
|
}
|
|
|
|
|
|
|
|
RunPreconnectScript(c);
|
|
|
|
|
|
|
|
/* Create an event object to signal OpenVPN to exit */
|
|
|
|
_sntprintf_0(exit_event_name, _T("%x%08x"), GetCurrentProcessId(), c->threadId);
|
|
|
|
c->exit_event = CreateEvent(NULL, TRUE, FALSE, exit_event_name);
|
|
|
|
if (c->exit_event == NULL)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_EVENT, exit_event_name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a management interface password */
|
|
|
|
GetRandomPassword(c->manage.password, passwd_len - 1);
|
|
|
|
|
|
|
|
find_free_tcp_port(&c->manage.skaddr);
|
|
|
|
|
|
|
|
/* Construct command line -- put log first */
|
|
|
|
_sntprintf_0(cmdline, _T("openvpn --log%ls \"%ls\" --config \"%ls\" "
|
|
|
|
"--setenv IV_GUI_VER \"%hs\" --setenv IV_SSO openurl,webauth,crtext --service %ls 0 --auth-retry interact "
|
|
|
|
"--management %hs %hd stdin --management-query-passwords %ls"
|
|
|
|
"--management-hold"),
|
|
|
|
(o.log_append ? _T("-append") : _T("")), c->log_path,
|
|
|
|
c->config_file, PACKAGE_STRING, exit_event_name,
|
|
|
|
inet_ntoa(c->manage.skaddr.sin_addr), ntohs(c->manage.skaddr.sin_port),
|
|
|
|
(o.proxy_source != config ? _T("--management-query-proxy ") : _T("")));
|
|
|
|
|
|
|
|
BOOL use_iservice = (o.iservice_admin && IsWindows7OrGreater()) || !IsUserAdmin();
|
|
|
|
/* Try to open the service pipe */
|
|
|
|
if (use_iservice && InitServiceIO(&c->iserv))
|
|
|
|
{
|
|
|
|
BOOL res = FALSE;
|
|
|
|
|
|
|
|
if (o.ovpn_engine == OPENVPN_ENGINE_OVPN3)
|
|
|
|
{
|
|
|
|
#ifdef ENABLE_OVPN3
|
|
|
|
char *request = PrepareStartJsonRequest(c, exit_event_name);
|
|
|
|
|
|
|
|
res = (request != NULL) && WritePipe(c->iserv.pipe, request, strlen(request));
|
|
|
|
free(request);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DWORD size = _tcslen(c->config_dir) + _tcslen(options) + passwd_len + 3;
|
|
|
|
TCHAR startup_info[1024];
|
|
|
|
|
|
|
|
if (!AuthorizeConfig(c))
|
|
|
|
{
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
CloseServiceIO(&c->iserv);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->hProcess = NULL;
|
|
|
|
c->manage.password[passwd_len - 1] = '\n';
|
|
|
|
|
|
|
|
/* Ignore pushed route-method when service is in use */
|
|
|
|
const wchar_t* extra_options = L" --pull-filter ignore route-method";
|
|
|
|
size += wcslen(extra_options);
|
|
|
|
|
|
|
|
_sntprintf_0(startup_info, L"%ls%lc%ls%ls%lc%.*hs", c->config_dir, L'\0',
|
|
|
|
options, extra_options, L'\0', passwd_len, c->manage.password);
|
|
|
|
c->manage.password[passwd_len - 1] = '\0';
|
|
|
|
|
|
|
|
res = WritePipe(c->iserv.pipe, startup_info, size * sizeof(TCHAR));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg (IDS_ERR_WRITE_SERVICE_PIPE);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
CloseServiceIO(&c->iserv);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef ENABLE_OVPN3
|
|
|
|
else if (o.ovpn_engine == OPENVPN_ENGINE_OVPN3)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_WRITE_SERVICE_PIPE);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
CloseServiceIO(&c->iserv);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Start OpenVPN directly */
|
|
|
|
DWORD priority;
|
|
|
|
STARTUPINFO si;
|
|
|
|
PROCESS_INFORMATION pi;
|
|
|
|
SECURITY_DESCRIPTOR sd;
|
|
|
|
|
|
|
|
/* Make I/O handles inheritable and accessible by all */
|
|
|
|
SECURITY_ATTRIBUTES sa = {
|
|
|
|
.nLength = sizeof(sa),
|
|
|
|
.lpSecurityDescriptor = &sd,
|
|
|
|
.bInheritHandle = TRUE
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_INIT_SEC_DESC);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (!SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_SET_SEC_DESC_ACL);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set process priority */
|
|
|
|
if (!SetProcessPriority(&priority))
|
|
|
|
{
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get a handle of the NUL device */
|
|
|
|
hNul = CreateFile(_T("NUL"), GENERIC_WRITE, FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, NULL);
|
|
|
|
if (hNul == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create the pipe for STDIN with only the read end inheritable */
|
|
|
|
if (!CreatePipe(&hStdInRead, &hStdInWrite, &sa, 0))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_PIPE_IN_READ);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (!SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_DUP_HANDLE_IN_WRITE);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fill in STARTUPINFO struct */
|
|
|
|
GetStartupInfo(&si);
|
|
|
|
si.cb = sizeof(si);
|
|
|
|
si.dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
si.hStdInput = hStdInRead;
|
|
|
|
si.hStdOutput = hNul;
|
|
|
|
si.hStdError = hNul;
|
|
|
|
|
|
|
|
/* Create an OpenVPN process for the connection */
|
|
|
|
if (!CreateProcess(o.exe_path, cmdline, NULL, NULL, TRUE,
|
|
|
|
priority | CREATE_NO_WINDOW, NULL, c->config_dir, &si, &pi))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_PROCESS, o.exe_path, cmdline, c->config_dir);
|
|
|
|
CloseHandle(c->exit_event);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandleEx(&hStdInRead);
|
|
|
|
CloseHandleEx(&hNul);
|
|
|
|
|
|
|
|
/* Pass management password to OpenVPN process */
|
|
|
|
c->manage.password[passwd_len - 1] = '\n';
|
|
|
|
WriteFile(hStdInWrite, c->manage.password, passwd_len, &written, NULL);
|
|
|
|
c->manage.password[passwd_len - 1] = '\0';
|
|
|
|
|
|
|
|
c->hProcess = pi.hProcess; /* Will be closed in the event loop on exit */
|
|
|
|
CloseHandle(pi.hThread);
|
|
|
|
}
|
|
|
|
|
|
|
|
retval = TRUE;
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (hStdInWrite && hStdInWrite != INVALID_HANDLE_VALUE)
|
|
|
|
CloseHandle(hStdInWrite);
|
|
|
|
if (hStdInRead && hStdInRead != INVALID_HANDLE_VALUE)
|
|
|
|
CloseHandle(hStdInRead);
|
|
|
|
if (hNul && hNul != INVALID_HANDLE_VALUE)
|
|
|
|
CloseHandle(hNul);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Close the status thread without disconnecting the tunnel.
|
|
|
|
* Meant to be used only on persistent connections which can
|
|
|
|
* stay connected without the GUI tending to it.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
DetachOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
/* currently supported only for persistent connections */
|
|
|
|
if (c->flags & FLAG_DAEMON_PERSISTENT)
|
|
|
|
{
|
|
|
|
c->auto_connect = false;
|
|
|
|
PostMessage(c->hwndStatus, WM_OVPN_DETACH, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
StopOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
PostMessage(c->hwndStatus, WM_OVPN_STOP, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* force-kill as a last resort */
|
|
|
|
static BOOL
|
|
|
|
TerminateOpenVPN (connection_t *c)
|
|
|
|
{
|
|
|
|
DWORD exit_code = 0;
|
|
|
|
BOOL retval = TRUE;
|
|
|
|
|
|
|
|
if (!c->hProcess)
|
|
|
|
return retval;
|
|
|
|
if (!GetExitCodeProcess (c->hProcess, &exit_code))
|
|
|
|
{
|
|
|
|
PrintDebug (L"In TerminateOpenVPN: failed to get process status: error = %lu", GetLastError());
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (exit_code == STILL_ACTIVE)
|
|
|
|
{
|
|
|
|
retval = TerminateProcess (c->hProcess, 1);
|
|
|
|
if (retval)
|
|
|
|
PrintDebug (L"Openvpn Process for config '%ls' terminated", c->config_name);
|
|
|
|
else
|
|
|
|
PrintDebug (L"Failed to terminate openvpn Process for config '%ls'", c->config_name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
PrintDebug(L"In TerminateOpenVPN: Process is not active");
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
RestartOpenVPN(connection_t *c)
|
|
|
|
{
|
|
|
|
if (c->state == onhold)
|
|
|
|
{
|
|
|
|
ReleaseOpenVPN(c);
|
|
|
|
}
|
|
|
|
else if (c->hwndStatus)
|
|
|
|
{
|
|
|
|
PostMessage(c->hwndStatus, WM_OVPN_RESTART, 0, 0);
|
|
|
|
}
|
|
|
|
else /* Not started: treat this as a request to connect */
|
|
|
|
{
|
|
|
|
StartOpenVPN(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SetStatusWinIcon(HWND hwndDlg, int iconId)
|
|
|
|
{
|
|
|
|
HICON hIcon = LoadLocalizedSmallIcon(iconId);
|
|
|
|
if (!hIcon)
|
|
|
|
return;
|
|
|
|
HICON hIconBig = LoadLocalizedIcon(ID_ICO_APP);
|
|
|
|
if (!hIconBig)
|
|
|
|
hIconBig = hIcon;
|
|
|
|
SendMessage(hwndDlg, WM_SETICON, (WPARAM) ICON_SMALL, (LPARAM) hIcon);
|
|
|
|
SendMessage(hwndDlg, WM_SETICON, (WPARAM) ICON_BIG, (LPARAM) hIconBig);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read one line from OpenVPN's stdout.
|
|
|
|
*/
|
|
|
|
static BOOL
|
|
|
|
ReadLineFromStdOut(HANDLE hStdOut, char *line, DWORD size)
|
|
|
|
{
|
|
|
|
DWORD len, read;
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
if (!PeekNamedPipe(hStdOut, line, size, &read, NULL, NULL))
|
|
|
|
{
|
|
|
|
if (GetLastError() != ERROR_BROKEN_PIPE)
|
|
|
|
ShowLocalizedMsg(IDS_ERR_READ_STDOUT_PIPE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *pos = memchr(line, '\r', read);
|
|
|
|
if (pos)
|
|
|
|
{
|
|
|
|
len = pos - line + 2;
|
|
|
|
if (len > size)
|
|
|
|
return FALSE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Line doesn't fit into the buffer */
|
|
|
|
if (read == size)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
Sleep(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ReadFile(hStdOut, line, len, &read, NULL) || read != len)
|
|
|
|
{
|
|
|
|
if (GetLastError() != ERROR_BROKEN_PIPE)
|
|
|
|
ShowLocalizedMsg(IDS_ERR_READ_STDOUT_PIPE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
line[read - 2] = '\0';
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
|
|
CheckVersion()
|
|
|
|
{
|
|
|
|
HANDLE hStdOutRead = NULL;
|
|
|
|
HANDLE hStdOutWrite = NULL;
|
|
|
|
BOOL retval = FALSE;
|
|
|
|
STARTUPINFO si;
|
|
|
|
PROCESS_INFORMATION pi;
|
|
|
|
TCHAR cmdline[] = _T("openvpn --version");
|
|
|
|
char match_version[] = "OpenVPN 2.";
|
|
|
|
TCHAR pwd[MAX_PATH];
|
|
|
|
char line[1024];
|
|
|
|
TCHAR *p;
|
|
|
|
|
|
|
|
CLEAR(si);
|
|
|
|
CLEAR(pi);
|
|
|
|
|
|
|
|
/* Make handles inheritable and accessible by all */
|
|
|
|
SECURITY_DESCRIPTOR sd;
|
|
|
|
SECURITY_ATTRIBUTES sa = {
|
|
|
|
.nLength = sizeof(sa),
|
|
|
|
.lpSecurityDescriptor = &sd,
|
|
|
|
.bInheritHandle = TRUE
|
|
|
|
};
|
|
|
|
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_INIT_SEC_DESC);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (!SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_SET_SEC_DESC_ACL);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create the pipe for STDOUT with inheritable write end */
|
|
|
|
if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_PIPE_IN_READ);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (!SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0))
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_DUP_HANDLE_IN_WRITE);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Construct the process' working directory */
|
|
|
|
_tcsncpy(pwd, o.exe_path, _countof(pwd));
|
|
|
|
p = _tcsrchr(pwd, _T('\\'));
|
|
|
|
if (p != NULL)
|
|
|
|
*p = _T('\0');
|
|
|
|
|
|
|
|
/* Fill in STARTUPINFO struct */
|
|
|
|
si.cb = sizeof(si);
|
|
|
|
si.dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
si.hStdOutput = hStdOutWrite;
|
|
|
|
si.hStdError = hStdOutWrite;
|
|
|
|
|
|
|
|
/* Start OpenVPN to check version */
|
|
|
|
bool success = CreateProcess(o.exe_path, cmdline, NULL, NULL, TRUE,
|
|
|
|
CREATE_NO_WINDOW, NULL, pwd, &si, &pi);
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
ShowLocalizedMsg(IDS_ERR_CREATE_PROCESS, o.exe_path, cmdline, pwd);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandleEx(&hStdOutWrite);
|
|
|
|
CloseHandleEx(&pi.hThread);
|
|
|
|
CloseHandleEx(&pi.hProcess);
|
|
|
|
|
|
|
|
if (ReadLineFromStdOut(hStdOutRead, line, sizeof(line)))
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
PrintDebug(_T("VersionString: %hs"), line);
|
|
|
|
#endif
|
|
|
|
/* OpenVPN version 2.x */
|
|
|
|
char *p = strstr(line, match_version);
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
retval = TRUE;
|
|
|
|
p = strtok(p+8, " ");
|
|
|
|
strncpy(o.ovpn_version, p, _countof(o.ovpn_version)-1);
|
|
|
|
o.ovpn_version[_countof(o.ovpn_version)-1] = '\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
CloseHandleEx(&hStdOutWrite);
|
|
|
|
CloseHandleEx(&hStdOutRead);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Delete saved passwords and reset the checkboxes to default */
|
|
|
|
void
|
|
|
|
ResetSavePasswords(connection_t *c)
|
|
|
|
{
|
|
|
|
if (ShowLocalizedMsgEx(MB_OKCANCEL, NULL, TEXT(PACKAGE_NAME), IDS_NFO_DELETE_PASS, c->config_name) == IDCANCEL)
|
|
|
|
return;
|
|
|
|
DeleteSavedPasswords(c->config_name);
|
|
|
|
c->flags &= ~(FLAG_SAVE_KEY_PASS | FLAG_SAVE_AUTH_PASS);
|
|
|
|
SetMenuStatus(c, c->state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* keep this array in same order as corresponding resource ids -- IDS_NFO_OVPN_STATE_xxxxx */
|
|
|
|
static const char *daemon_states[] = {"INITIAL", "CONNECTING", "ASSIGN_IP",
|
|
|
|
"ADD_ROUTES", "CONNECTED", "RECONNECTING", "EXITING", "WAIT",
|
|
|
|
"AUTH", "GET_CONFIG", "RESOLVE", "TCP_CONNECT", "AUTH_PENDING", NULL};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given an OpenVPN state as reported by the management interface
|
|
|
|
* return the correspnding resource id for translated string.
|
|
|
|
* Returns IDS_NFO_OVPN_STATE_UNKNOWN if no match found.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
daemon_state_resid(const char *state)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
while (daemon_states[i] && strcmp(daemon_states[i], state))
|
|
|
|
{
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return daemon_states[i] ? IDS_NFO_OVPN_STATE_INITIAL + i : IDS_NFO_OVPN_STATE_UNKNOWN;
|
|
|
|
}
|