Read errors from the service pipe and handle fatal ones

Asynchronously read Input on the service pipe which are mostly
errors reported by the service. Display the errors on the status log
window and to the log file if its not opened by openvpn.
If/when openvpn fails to start or exits with error, close
the connection without waiting for management socket timeout.

v2:
- rebase to master
- fix a bug in setting manage.connected state
- ensure management socket is closed and resources freed before thread exit

Signed-off-by: Selva Nair <selva.nair@gmail.com>
pull/28/head
Selva Nair 2016-02-22 08:12:55 -05:00
parent 2175aee489
commit 854d76ae31
5 changed files with 343 additions and 28 deletions

View File

@ -65,10 +65,13 @@ OpenManagement(connection_t *c)
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return FALSE;
c->manage.connected = FALSE;
c->manage.sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (c->manage.sk == INVALID_SOCKET)
{
WSACleanup ();
return FALSE;
}
if (WSAAsyncSelect(c->manage.sk, c->hwndStatus, WM_MANAGEMENT,
FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE) != 0)
return FALSE;
@ -206,10 +209,14 @@ OnManagement(SOCKET sk, LPARAM lParam)
else
{
/* Connection to MI timed out. */
c->state = timedout;
if (c->state != disconnected)
c->state = timedout;
CloseManagement (c);
rtmsg_handler[stop](c, "");
}
}
else
c->manage.connected = TRUE;
break;
case FD_READ:
@ -340,6 +347,18 @@ OnManagement(SOCKET sk, LPARAM lParam)
break;
case FD_CLOSE:
CloseManagement (c);
if (rtmsg_handler[stop])
rtmsg_handler[stop](c, "");
break;
}
}
void
CloseManagement(connection_t *c)
{
if (c->manage.sk != INVALID_SOCKET)
{
if (c->manage.saved_size)
{
free(c->manage.saved_data);
@ -348,11 +367,9 @@ OnManagement(SOCKET sk, LPARAM lParam)
}
closesocket(c->manage.sk);
c->manage.sk = INVALID_SOCKET;
c->manage.connected = FALSE;
while (UnqueueCommand(c))
;
WSACleanup();
if (rtmsg_handler[stop])
rtmsg_handler[stop](c, "");
break;
}
}

View File

@ -68,5 +68,6 @@ BOOL OpenManagement(connection_t *);
BOOL ManagementCommand(connection_t *, char *, mgmt_msg_func, mgmt_cmd_type);
void OnManagement(SOCKET, LPARAM);
void CloseManagement(connection_t *);
#endif

325
openvpn.c
View File

@ -3,6 +3,7 @@
*
* 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
@ -31,6 +32,7 @@
#include <stdio.h>
#include <process.h>
#include <richedit.h>
#include <time.h>
#include "tray.h"
#include "main.h"
@ -466,6 +468,269 @@ OnStop(connection_t *c, UNUSED char *msg)
}
}
/*
* 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
HandleServiceIO (DWORD err, DWORD bytes, LPOVERLAPPED lpo)
{
service_io_t *s = (service_io_t *) lpo;
int len, capacity;
len = _countof(s->readbuf);
capacity = len*sizeof(*(s->readbuf));
if (bytes > 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, (LPOVERLAPPED_COMPLETION_ROUTINE) 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;
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;
while (iswspace(*p)) ++p;
while (p && *p)
{
next = WrapLine (p);
WriteStatusLog (c, prefix, p, c->manage.connected ? FALSE : TRUE);
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);
}
/*
* Close open handles
*/
static void
Cleanup (connection_t *c)
{
CloseManagement (c);
if (c->hProcess)
CloseHandle (c->hProcess);
else
CloseServiceIO (&c->iserv);
c->hProcess = NULL;
if (c->exit_event)
CloseHandle (c->exit_event);
c->exit_event = NULL;
}
/*
* DialogProc for OpenVPN status dialog windows
@ -587,7 +852,6 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
return FALSE;
}
/*
* ThreadProc for OpenVPN status dialog windows
*/
@ -597,6 +861,9 @@ 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));
@ -617,12 +884,35 @@ ThreadOpenVPNStatus(void *p)
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->hProcess)
{
HandleServiceIO (0, 0, (LPOVERLAPPED) &c->iserv);
wait_event = c->iserv.hEvent;
}
else
wait_event = c->hProcess;
if (o.silent_connection[0] == '0')
ShowWindow(c->hwndStatus, SW_SHOW);
/* Run the message loop for the status window */
while (GetMessage(&msg, NULL, 0, 0))
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 (c->hProcess)
OnProcess (c, NULL);
else
OnService (c, NULL);
}
continue;
}
if (msg.hwnd == NULL)
{
switch (msg.message)
@ -653,10 +943,12 @@ ThreadOpenVPNStatus(void *p)
DispatchMessage(&msg);
}
}
/* release handles etc.*/
Cleanup (c);
return 0;
}
/*
* Set priority based on the registry or cmd-line value
*/
@ -682,7 +974,6 @@ SetProcessPriority(DWORD *priority)
return TRUE;
}
/*
* Launch an OpenVPN process and the accompanying thread to monitor it
*/
@ -693,7 +984,7 @@ StartOpenVPN(connection_t *c)
TCHAR *options = cmdline + 8;
TCHAR exit_event_name[17];
HANDLE hStdInRead = NULL, hStdInWrite = NULL;
HANDLE hNul = NULL, hThread = NULL, service = NULL;
HANDLE hNul = NULL, hThread = NULL;
DWORD written;
BOOL retval = FALSE;
@ -739,15 +1030,10 @@ StartOpenVPN(connection_t *c)
(o.proxy_source != config ? _T("--management-query-proxy ") : _T("")));
/* Try to open the service pipe */
if (!IsUserAdmin())
service = CreateFile(_T("\\\\.\\pipe\\openvpn\\service"),
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (service && service != INVALID_HANDLE_VALUE)
if (!IsUserAdmin() && InitServiceIO (&c->iserv))
{
DWORD size = _tcslen(c->config_dir) + _tcslen(options) + sizeof(c->manage.password) + 3;
TCHAR startup_info[1024];
DWORD dwMode = PIPE_READMODE_MESSAGE;
if ( !AuthorizeConfig(c))
{
@ -755,22 +1041,17 @@ StartOpenVPN(connection_t *c)
goto out;
}
if (!SetNamedPipeHandleState(service, &dwMode, NULL, NULL))
{
ShowLocalizedMsg (IDS_ERR_ACCESS_SERVICE_PIPE);
CloseHandle(c->exit_event);
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 (!WriteFile(service, startup_info, size * sizeof (TCHAR), &written, NULL))
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;
}
}
@ -788,6 +1069,7 @@ StartOpenVPN(connection_t *c)
.lpSecurityDescriptor = &sd,
.bInheritHandle = TRUE
};
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
{
ShowLocalizedMsg(IDS_ERR_INIT_SEC_DESC);
@ -852,7 +1134,7 @@ StartOpenVPN(connection_t *c)
WriteFile(hStdInWrite, c->manage.password, sizeof(c->manage.password), &written, NULL);
c->manage.password[sizeof(c->manage.password) - 1] = '\0';
CloseHandle(pi.hProcess);
c->hProcess = pi.hProcess; /* Will be closed in the event loop on exit */
CloseHandle(pi.hThread);
}
@ -861,8 +1143,6 @@ StartOpenVPN(connection_t *c)
retval = TRUE;
out:
if (service && service != INVALID_HANDLE_VALUE)
CloseHandle(service);
if (hThread && hThread != INVALID_HANDLE_VALUE)
CloseHandle(hThread);
if (hStdInWrite && hStdInWrite != INVALID_HANDLE_VALUE)
@ -1030,4 +1310,3 @@ out:
CloseHandle(hStdOutWrite);
return retval;
}

View File

@ -38,4 +38,10 @@ void OnStop(connection_t *, char *);
extern const TCHAR *cfgProp;
/* These error codes are from openvpn service sources */
#define ERROR_OPENVPN_STARTUP 0x20000000
#define ERROR_STARTUP_DATA 0x20000001
#define ERROR_MESSAGE_DATA 0x20000002
#define ERROR_MESSAGE_TYPE 0x20000003
#endif

View File

@ -74,6 +74,14 @@ typedef enum {
timedout
} conn_state_t;
/* Interactive Service IO parameters */
typedef struct {
OVERLAPPED o; /* This has to be the first element */
HANDLE pipe;
HANDLE hEvent;
WCHAR readbuf[512];
} service_io_t;
/* Connections parameters */
struct connection {
TCHAR config_file[MAX_PATH]; /* Name of the config file */
@ -95,8 +103,12 @@ struct connection {
char *saved_data;
size_t saved_size;
mgmt_cmd_t *cmd_queue;
BOOL connected; /* True, if management interface has connected */
} manage;
HANDLE hProcess; /* Handle of openvpn process if directly started */
service_io_t iserv;
HANDLE exit_event;
DWORD threadId;
HWND hwndStatus;