mirror of https://github.com/OpenVPN/openvpn-gui
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1939 lines
57 KiB
1939 lines
57 KiB
/* |
|
* 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 <tchar.h> |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <process.h> |
|
#include <richedit.h> |
|
#include <time.h> |
|
|
|
#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" |
|
|
|
#define WM_OVPN_STOP (WM_APP + 10) |
|
#define WM_OVPN_SUSPEND (WM_APP + 11) |
|
|
|
extern options_t o; |
|
|
|
static BOOL |
|
TerminateOpenVPN(connection_t *c); |
|
|
|
const TCHAR *cfgProp = _T("conn"); |
|
|
|
#define FLAG_CR_TYPE_SCRV1 0x1 /* static challenege */ |
|
#define FLAG_CR_TYPE_CRV1 0x2 /* dynamic challenege */ |
|
#define FLAG_CR_ECHO 0x4 /* echo the response */ |
|
#define FLAG_CR_RESPONSE 0x8 /* response needed */ |
|
#define FLAG_PASS_TOKEN 0x10 /* PKCS11 token password needed */ |
|
#define FLAG_STRING_PKCS11 0x20 /* PKCS11 id needed */ |
|
#define FLAG_PASS_PKEY 0x40 /* Private key password needed */ |
|
|
|
typedef struct { |
|
connection_t *c; |
|
unsigned int flags; |
|
char *str; |
|
char *id; |
|
char *user; |
|
} auth_param_t; |
|
|
|
static void |
|
WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio); |
|
|
|
static void |
|
free_auth_param (auth_param_t *param) |
|
{ |
|
if (!param) |
|
return; |
|
free (param->str); |
|
free (param->id); |
|
free (param->user); |
|
free (param); |
|
} |
|
|
|
void |
|
AppendTextToCaption (HANDLE hwnd, const WCHAR *str) |
|
{ |
|
WCHAR old[256]; |
|
WCHAR new[256]; |
|
GetWindowTextW (hwnd, old, _countof(old)); |
|
_sntprintf_0 (new, L"%s (%s)", old, str); |
|
SetWindowText (hwnd, new); |
|
} |
|
|
|
/* |
|
* 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 all on", OnLogLine, combined); |
|
ManagementCommand(c, "echo all on", OnEcho, combined); |
|
} |
|
|
|
|
|
/* |
|
* Handle the request to release a hold from the OpenVPN management interface |
|
*/ |
|
void |
|
OnHold(connection_t *c, UNUSED char *msg) |
|
{ |
|
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; |
|
|
|
/* 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(' '); |
|
|
|
/* Append line to log window */ |
|
SendMessage(logWnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1); |
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) datetime); |
|
SendMessage(logWnd, EM_SETTEXTEX, (WPARAM) &ste, (LPARAM) message); |
|
SendMessage(logWnd, EM_REPLACESEL, FALSE, (LPARAM) _T("\n")); |
|
} |
|
|
|
|
|
/* |
|
* 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; |
|
|
|
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'; |
|
|
|
if (strcmp(state, "CONNECTED") == 0 && strcmp(message, "SUCCESS") == 0) |
|
{ |
|
/* Run Connect Script */ |
|
if (c->state == connecting || c->state == resuming) |
|
RunConnectScript(c, false); |
|
|
|
/* Save the local IP address if available */ |
|
char *local_ip = pos + 1; |
|
pos = strchr(local_ip, ','); |
|
if (pos != NULL) |
|
*pos = '\0'; |
|
|
|
/* Convert the IP address to Unicode */ |
|
MultiByteToWideChar(CP_UTF8, 0, local_ip, -1, c->ip, _countof(c->ip)); |
|
|
|
/* 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]; |
|
LoadLocalizedStringBuf(msg, _countof(msg), IDS_NFO_NOW_CONNECTED, c->config_name); |
|
ShowTrayBalloon(msg, (_tcslen(c->ip) ? LoadLocalizedString(IDS_NFO_ASSIGN_IP, c->ip) : _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)); |
|
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++; |
|
|
|
if (strcmp(message, "auth-failure") == 0 && (c->flags & FLAG_SAVE_AUTH_PASS)) |
|
SaveAuthPass(c->config_name, L""); /* clear saved password */ |
|
|
|
else if (strcmp(message, "private-key-password-failure") == 0 && (c->flags & FLAG_SAVE_KEY_PASS)) |
|
SaveKeyPass(c->config_name, L""); /* clear saved private key password */ |
|
} |
|
|
|
c->state = reconnecting; |
|
CheckAndSetTrayIcon(); |
|
|
|
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING)); |
|
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING); |
|
} |
|
} |
|
|
|
/* |
|
* 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]; |
|
WCHAR password[USER_PASS_LEN]; |
|
|
|
switch (msg) |
|
{ |
|
case WM_INITDIALOG: |
|
/* Set connection for this dialog and show it */ |
|
param = (auth_param_t *) lParam; |
|
SetProp(hwndDlg, cfgProp, (HANDLE) param); |
|
|
|
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); |
|
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); |
|
|
|
AppendTextToCaption (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_COMMAND: |
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp); |
|
switch (LOWORD(wParam)) |
|
{ |
|
case ID_EDT_AUTH_USER: |
|
if (HIWORD(wParam) == EN_UPDATE) |
|
{ |
|
int len = Edit_GetTextLength((HWND) lParam); |
|
EnableWindow(GetDlgItem(hwndDlg, IDOK), (len ? TRUE : FALSE)); |
|
} |
|
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); |
|
} |
|
break; |
|
|
|
case IDOK: |
|
if (GetDlgItemTextW(hwndDlg, ID_EDT_AUTH_USER, username, _countof(username))) |
|
{ |
|
SaveUsername(param->c->config_name, username); |
|
} |
|
if ( param->c->flags & FLAG_SAVE_AUTH_PASS && |
|
GetDlgItemTextW(hwndDlg, ID_EDT_AUTH_PASS, password, _countof(password)) && |
|
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) |
|
ManagementCommandFromInputBase64(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) |
|
{ |
|
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: |
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp); |
|
free_auth_param (param); |
|
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; |
|
|
|
switch (msg) |
|
{ |
|
case WM_INITDIALOG: |
|
param = (auth_param_t *) lParam; |
|
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) |
|
{ |
|
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); |
|
} |
|
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); |
|
|
|
break; |
|
|
|
case WM_COMMAND: |
|
param = (auth_param_t *) GetProp(hwndDlg, cfgProp); |
|
const char *template; |
|
char *fmt; |
|
switch (LOWORD(wParam)) |
|
{ |
|
case IDOK: |
|
if (param->flags & FLAG_CR_TYPE_CRV1) |
|
{ |
|
/* send username */ |
|
template = "username \"Auth\" \"%s\""; |
|
fmt = malloc(strlen(template) + strlen(param->user)); |
|
|
|
if (fmt) |
|
{ |
|
sprintf(fmt, template, param->user); |
|
ManagementCommand(param->c, fmt, NULL, regular); |
|
free(fmt); |
|
} |
|
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); |
|
} |
|
|
|
/* 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: '%S'", 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_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; |
|
SetProp(hwndDlg, cfgProp, (HANDLE) c); |
|
AppendTextToCaption (hwndDlg, c->config_name); |
|
if (RecallKeyPass(c->config_name, passphrase) && wcslen(passphrase)) |
|
{ |
|
/* 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); |
|
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 IDOK: |
|
if ((c->flags & FLAG_SAVE_KEY_PASS) && |
|
GetDlgItemTextW(hwndDlg, ID_EDT_PASSPHRASE, passphrase, _countof(passphrase)) && |
|
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. |
|
*/ |
|
static 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" */ |
|
for (i = 0, p1 = p; i < 4; ++i, p1 = NULL) |
|
{ |
|
token[i] = strtok (p1, ":"); /* 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 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: '%S' '%S' '%S' '%S'", 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 = '%S' str = '%S' flags = %u", |
|
param->id, param->str, param->flags); |
|
ret = TRUE; |
|
|
|
out: |
|
free (p); |
|
if (!ret) |
|
PrintDebug (L"Error parsing password/string request msg: <%S>", msg); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Handle >ECHO: request from OpenVPN management interface |
|
* Expect msg = timestamp,message |
|
*/ |
|
void |
|
OnEcho(connection_t *c, char *msg) |
|
{ |
|
WCHAR errmsg[256]; |
|
|
|
PrintDebug(L"OnEcho with msg = %S", 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) |
|
{ |
|
c->flags |= (FLAG_SAVE_KEY_PASS | FLAG_SAVE_AUTH_PASS); |
|
} |
|
else |
|
{ |
|
_sntprintf_0(errmsg, L"WARNING: Unknown ECHO directive '%S' 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 = %S", 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 */ |
|
|
|
/* Check if a response is required: ie., starts with R or E,R */ |
|
if (strncmp (chstr, "R", 1) != 0 && strncmp (chstr, "E,R", 3) != 0) |
|
{ |
|
PrintDebug(L"Got dynamic challenge request with no response required: <%S>", chstr); |
|
return; |
|
} |
|
|
|
/* 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: <%S>", 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 exit of the OpenVPN process |
|
*/ |
|
void |
|
OnStop(connection_t *c, UNUSED char *msg) |
|
{ |
|
UINT txt_id, msg_id; |
|
TCHAR *msg_xtra; |
|
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); |
|
} |
|
MessageBox(c->hwndStatus, LoadLocalizedString(IDS_NFO_CONN_TERMINATED, c->config_file), |
|
_T(PACKAGE_NAME), MB_OK); |
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0); |
|
break; |
|
|
|
case resuming: |
|
case connecting: |
|
case reconnecting: |
|
case timedout: |
|
/* 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; |
|
msg_xtra = c->state == timedout ? c->log_path : c->config_name; |
|
if (c->state == timedout) |
|
msg_id = IDS_NFO_CONN_TIMEOUT; |
|
|
|
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); |
|
} |
|
MessageBox(c->hwndStatus, LoadLocalizedString(msg_id, msg_xtra), _T(PACKAGE_NAME), MB_OK); |
|
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0); |
|
break; |
|
|
|
case disconnecting: |
|
// /* Check for "certificate has expired" message */ |
|
// if ((strstr(line, "error=certificate has expired") != NULL)) |
|
// { |
|
// StopOpenVPN(config); |
|
// /* Cert expired... */ |
|
// ShowLocalizedMsg(IDS_ERR_CERT_EXPIRED); |
|
// } |
|
// |
|
// /* Check for "certificate is not yet valid" message */ |
|
// if ((strstr(line, "error=certificate is not yet valid") != NULL)) |
|
// { |
|
// StopOpenVPN(config); |
|
// /* Cert not yet valid */ |
|
// ShowLocalizedMsg(IDS_ERR_CERT_NOT_YET_VALID); |
|
// } |
|
/* Shutdown was initiated by us */ |
|
c->failed_psw_attempts = 0; |
|
c->failed_auth_attempts = 0; |
|
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; |
|
} |
|
} |
|
|
|
/* |
|
* 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 |
|
*/ |
|
static void |
|
WriteStatusLog (connection_t *c, const WCHAR *prefix, const WCHAR *line, BOOL fileio) |
|
{ |
|
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' '; |
|
|
|
/* 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"%s%s%s\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 = 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(_T("\\\\.\\pipe\\openvpn\\service"), |
|
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 the first 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); |
|
} |
|
if (err) |
|
{ |
|
_snwprintf(s->readbuf, len, L"0x%08x\nInteractive Service disconnected\n", err); |
|
s->readbuf[len-1] = L'\0'; |
|
SetEvent (s->hEvent); |
|
return; |
|
} |
|
|
|
/* 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; |
|
DWORD len; |
|
const WCHAR *prefix = L"IService> "; |
|
|
|
len = wcslen (c->iserv.readbuf); |
|
if (!len || (buf = wcsdup (c->iserv.readbuf)) == 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; |
|
if (!err && swscanf (p, L"0x%08x\nProcess ID", &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); |
|
c->state = timedout; /* Force the popup message to include the log file name */ |
|
OnStop (c, NULL); |
|
break; |
|
case ERROR_OPENVPN_STARTUP: |
|
WriteStatusLog (c, prefix, L"Check the log file for details", false); |
|
c->state = timedout; /* Force the popup message to include the log file name */ |
|
OnStop(c, NULL); |
|
break; |
|
default: |
|
/* Unknown failure: let management connection timeout */ |
|
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 (MessageBoxW (NULL, wstr, L""PACKAGE_NAME, MB_OKCANCEL) == 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) |
|
{ |
|
WriteStatusLog (c, L"GUI> ", L"Error: Received NEED-STR message -- not implemented", false); |
|
} |
|
|
|
/* |
|
* Close open handles |
|
*/ |
|
static void |
|
Cleanup (connection_t *c) |
|
{ |
|
CloseManagement (c); |
|
|
|
free_dynamic_cr (c); |
|
|
|
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; |
|
} |
|
/* |
|
* 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(70), TRUE); |
|
MoveWindow(GetDlgItem(hwndDlg, ID_TXT_STATUS), DPI_SCALE(20), DPI_SCALE(5), w - DPI_SCALE(25), 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_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 */ |
|
SetProp(hwndDlg, cfgProp, (HANDLE) c); |
|
|
|
/* 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); |
|
|
|
/* Set size and position of controls */ |
|
RECT rect; |
|
GetClientRect(hwndDlg, &rect); |
|
RenderStatusWindow(hwndDlg, rect.right, rect.bottom); |
|
/* Set focus on the LogWindow so it scrolls automatically */ |
|
SetFocus(hLogWnd); |
|
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: |
|
c->state = reconnecting; |
|
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG)); |
|
ManagementCommand(c, "signal SIGHUP", NULL, regular); |
|
return TRUE; |
|
} |
|
break; |
|
|
|
case WM_SHOWWINDOW: |
|
if (wParam == TRUE) |
|
{ |
|
c = (connection_t *) GetProp(hwndDlg, cfgProp); |
|
if (c->hwndStatus) |
|
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG)); |
|
} |
|
return FALSE; |
|
|
|
case WM_CLOSE: |
|
c = (connection_t *) GetProp(hwndDlg, cfgProp); |
|
if (c->state != disconnected) |
|
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_STOP: |
|
c = (connection_t *) GetProp(hwndDlg, cfgProp); |
|
c->state = disconnecting; |
|
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)); |
|
SetEvent(c->exit_event); |
|
SetTimer(hwndDlg, IDT_STOP_TIMER, 3000, 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, 3000, 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); |
|
} |
|
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); |
|
|
|
/* 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'); |
|
|
|
c->state = (c->state == suspended ? resuming : connecting); |
|
|
|
/* Create and Show Status Dialog */ |
|
c->hwndStatus = CreateLocalizedDialogParam(ID_DLG_STATUS, StatusDialogFunc, (LPARAM) c); |
|
if (!c->hwndStatus) |
|
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)) |
|
PostMessage(c->hwndStatus, WM_CLOSE, 0, 0); |
|
|
|
/* 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; |
|
|
|
if (o.silent_connection == 0) |
|
ShowWindow(c->hwndStatus, SW_SHOW); |
|
|
|
/* Run the message loop for the status window */ |
|
while (WM_QUIT != msg.message) |
|
{ |
|
DWORD res; |
|
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; |
|
} |
|
|
|
/* |
|
* Launch an OpenVPN process and the accompanying thread to monitor it |
|
*/ |
|
BOOL |
|
StartOpenVPN(connection_t *c) |
|
{ |
|
TCHAR cmdline[1024]; |
|
TCHAR *options = cmdline + 8; |
|
TCHAR exit_event_name[17]; |
|
HANDLE hStdInRead = NULL, hStdInWrite = NULL; |
|
HANDLE hNul = NULL, hThread = NULL; |
|
DWORD written; |
|
BOOL retval = FALSE; |
|
|
|
CLEAR(c->ip); |
|
|
|
if (c->hwndStatus) |
|
{ |
|
PrintDebug(L"Connection request when previous status window is still open -- ignored"); |
|
WriteStatusLog(c, L"OpenVPN GUI> ", |
|
L"Complete the pending dialog before starting a new connection", false); |
|
SetForegroundWindow(c->hwndStatus); |
|
return FALSE; |
|
} |
|
|
|
RunPreconnectScript(c); |
|
|
|
/* Create thread to show the connection's status dialog */ |
|
hThread = CreateThread(NULL, 0, ThreadOpenVPNStatus, c, CREATE_SUSPENDED, &c->threadId); |
|
if (hThread == NULL) |
|
{ |
|
ShowLocalizedMsg(IDS_ERR_CREATE_THREAD_STATUS); |
|
goto out; |
|
} |
|
|
|
/* 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, sizeof(c->manage.password) - 1); |
|
|
|
/* Construct command line -- put log first */ |
|
_sntprintf_0(cmdline, _T("openvpn --log%s \"%s\" --config \"%s\" " |
|
"--setenv IV_GUI_VER \"%S\" --service %s 0 --auth-retry interact " |
|
"--management %S %hd stdin --management-query-passwords %s" |
|
"--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(""))); |
|
|
|
/* Try to open the service pipe */ |
|
if (!IsUserAdmin() && InitServiceIO (&c->iserv)) |
|
{ |
|
DWORD size = _tcslen(c->config_dir) + _tcslen(options) + sizeof(c->manage.password) + 3; |
|
TCHAR startup_info[1024]; |
|
|
|
if ( !AuthorizeConfig(c)) |
|
{ |
|
CloseHandle(c->exit_event); |
|
CloseServiceIO(&c->iserv); |
|
goto out; |
|
} |
|
|
|
c->hProcess = NULL; |
|
c->manage.password[sizeof(c->manage.password) - 1] = '\n'; |
|
_sntprintf_0(startup_info, _T("%s%c%s%c%.*S"), c->config_dir, _T('\0'), |
|
options, _T('\0'), sizeof(c->manage.password), c->manage.password); |
|
c->manage.password[sizeof(c->manage.password) - 1] = '\0'; |
|
|
|
if (!WritePipe(c->iserv.pipe, startup_info, size * sizeof (TCHAR))) |
|
{ |
|
ShowLocalizedMsg (IDS_ERR_WRITE_SERVICE_PIPE); |
|
CloseHandle(c->exit_event); |
|
CloseServiceIO(&c->iserv); |
|
goto out; |
|
} |
|
} |
|
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; |
|
} |
|
|
|
/* Pass management password to OpenVPN process */ |
|
c->manage.password[sizeof(c->manage.password) - 1] = '\n'; |
|
WriteFile(hStdInWrite, c->manage.password, sizeof(c->manage.password), &written, NULL); |
|
c->manage.password[sizeof(c->manage.password) - 1] = '\0'; |
|
|
|
c->hProcess = pi.hProcess; /* Will be closed in the event loop on exit */ |
|
CloseHandle(pi.hThread); |
|
} |
|
|
|
/* Start the status dialog thread */ |
|
ResumeThread(hThread); |
|
retval = TRUE; |
|
|
|
out: |
|
if (hThread && hThread != INVALID_HANDLE_VALUE) |
|
CloseHandle(hThread); |
|
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; |
|
} |
|
|
|
|
|
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 '%s' terminated", c->config_name); |
|
else |
|
PrintDebug (L"Failed to terminate openvpn Process for config '%s'", c->config_name); |
|
} |
|
else |
|
PrintDebug(L"In TerminateOpenVPN: Process is not active"); |
|
|
|
return retval; |
|
} |
|
|
|
void |
|
SuspendOpenVPN(int config) |
|
{ |
|
PostMessage(o.conn[config].hwndStatus, WM_OVPN_SUSPEND, 0, 0); |
|
} |
|
|
|
|
|
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; |
|
HANDLE hStdOutWrite; |
|
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 */ |
|
if (!CreateProcess(o.exe_path, cmdline, NULL, NULL, TRUE, |
|
CREATE_NO_WINDOW, NULL, pwd, &si, &pi)) |
|
{ |
|
ShowLocalizedMsg(IDS_ERR_CREATE_PROCESS, o.exe_path, cmdline, pwd); |
|
} |
|
else if (ReadLineFromStdOut(hStdOutRead, line, sizeof(line))) |
|
{ |
|
#ifdef DEBUG |
|
PrintDebug(_T("VersionString: %S"), line); |
|
#endif |
|
CloseHandle(pi.hThread); |
|
CloseHandle(pi.hProcess); |
|
|
|
/* 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: |
|
CloseHandle(hStdOutRead); |
|
CloseHandle(hStdOutWrite); |
|
return retval; |
|
} |
|
|
|
/* Delete saved passwords and reset the checkboxes to default */ |
|
void |
|
ResetSavePasswords(connection_t *c) |
|
{ |
|
if (ShowLocalizedMsgEx(MB_OKCANCEL, 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); |
|
}
|
|
|