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.

514 lines
15 KiB

/*
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
*
* Copyright (C) 2010 Heiko Hund <heikoh@users.sf.net>
*
* 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 <winsock2.h>
#include <malloc.h>
#include "options.h"
#include "manage.h"
#include "main.h"
#include "misc.h"
extern options_t o;
static mgmt_msg_func rtmsg_handler[mgmt_rtmsg_type_max];
/*
* Number of seconds to try connecting to management interface
*/
static const time_t max_connect_time = 15;
/*
* Initialize the real-time notification handlers
*/
void
InitManagement(const mgmt_rtmsg_handler *handler)
{
int i;
for (i = 0; handler[i].handler; ++i)
{
rtmsg_handler[handler[i].type] = handler[i].handler;
}
}
/*
* Connect to the OpenVPN management interface and register
* asynchronous socket event notification for it
*/
BOOL
OpenManagement(connection_t *c)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return FALSE;
}
c->manage.connected = 0;
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;
}
connect(c->manage.sk, (SOCKADDR *)&c->manage.skaddr, sizeof(c->manage.skaddr));
c->manage.timeout = time(NULL) + max_connect_time;
return TRUE;
}
/*
* Try to send a queued management command to OpenVPN
*/
static void
SendCommand(connection_t *c)
{
int res;
mgmt_cmd_t *cmd = c->manage.cmd_queue;
if (cmd == NULL || cmd->size == 0)
{
return;
}
res = send(c->manage.sk, cmd->command, cmd->size, 0);
if (res < 1)
{
return;
}
if (res != cmd->size)
{
memmove(cmd->command, cmd->command + res, cmd->size - res);
}
cmd->size -= res;
}
/*
* Send a command to the OpenVPN management interface
*/
BOOL
ManagementCommand(connection_t *c, char *command, mgmt_msg_func handler, mgmt_cmd_type type)
{
mgmt_cmd_t *cmd = calloc(1, sizeof(*cmd));
if (cmd == NULL)
{
return FALSE;
}
cmd->size = strlen(command) + 1;
cmd->command = malloc(cmd->size);
if (cmd->command == NULL)
{
free(cmd);
return FALSE;
}
memcpy(cmd->command, command, cmd->size);
*(cmd->command + cmd->size - 1) = '\n';
cmd->handler = handler;
cmd->type = type;
if (c->manage.cmd_queue)
{
cmd->next = c->manage.cmd_queue;
cmd->prev = c->manage.cmd_queue->prev;
cmd->next->prev = cmd->prev->next = cmd;
}
else
{
cmd->next = cmd->prev = cmd;
c->manage.cmd_queue = cmd;
}
if (c->manage.cmd_queue == cmd)
{
SendCommand(c);
}
return TRUE;
}
/*
* Remove a command from a connection's command queue
*/
static BOOL
UnqueueCommand(connection_t *c)
{
mgmt_cmd_t *cmd = c->manage.cmd_queue;
if (!cmd)
{
return FALSE;
}
/* Wipe command as it may contain passwords */
memset(cmd->command, 'x', cmd->size);
if (cmd->type == combined)
{
cmd->type = regular;
return TRUE;
}
if (cmd->next == cmd)
{
c->manage.cmd_queue = NULL;
}
else
{
cmd->prev->next = cmd->next;
cmd->next->prev = cmd->prev;
c->manage.cmd_queue = cmd->next;
SendCommand(c);
}
free(cmd->command);
free(cmd);
return TRUE;
}
/*
* Handle management socket events asynchronously
*/
void
OnManagement(SOCKET sk, LPARAM lParam)
{
int res;
char *data;
ULONG data_size, offset;
connection_t *c = GetConnByManagement(sk);
if (c == NULL)
{
return;
}
switch (WSAGETSELECTEVENT(lParam))
{
case FD_CONNECT:
if (WSAGETSELECTERROR(lParam))
{
/* keep trying for connections with persistent daemons */
if (c->flags & FLAG_DAEMON_PERSISTENT
|| time(NULL) < c->manage.timeout)
{
/* show a message on status window */
if (rtmsg_handler[log_] && (c->flags & FLAG_DAEMON_PERSISTENT))
{
char buf[256];
_snprintf_0(buf, "%lld,W,Waiting for the management interface to come up",
(long long)time(NULL))
rtmsg_handler[log_](c, buf);
}
connect(c->manage.sk, (SOCKADDR *)&c->manage.skaddr, sizeof(c->manage.skaddr));
}
else
{
/* Connection to MI timed out. */
CloseManagement(c);
if (c->state != disconnected)
{
rtmsg_handler[timeout_](c, "");
}
}
}
else
{
c->manage.connected = 1;
}
break;
case FD_READ:
if (ioctlsocket(c->manage.sk, FIONREAD, &data_size) != 0
|| data_size == 0)
{
return;
}
data = malloc(c->manage.saved_size + data_size);
if (data == NULL)
{
return;
}
res = recv(c->manage.sk, data + c->manage.saved_size, data_size, 0);
if (res != (int) data_size)
{
free(data);
return;
}
/* Copy previously saved management data */
if (c->manage.saved_size)
{
memcpy(data, c->manage.saved_data, c->manage.saved_size);
data_size += c->manage.saved_size;
free(c->manage.saved_data);
c->manage.saved_data = NULL;
c->manage.saved_size = 0;
}
offset = 0;
while (offset < data_size)
{
char *pos;
char *line = data + offset;
size_t line_size = data_size - offset;
BOOL passwd_request = false;
const char *passwd_prompt = "ENTER PASSWORD:";
if (line_size >= strlen(passwd_prompt)
&& memcmp(line, passwd_prompt, strlen(passwd_prompt)) == 0)
{
pos = memchr(line, ':', line_size);
passwd_request = true;
}
else
{
pos = memchr(line, '\n', line_size);
}
if (pos == NULL)
{
c->manage.saved_data = malloc(line_size);
if (c->manage.saved_data)
{
c->manage.saved_size = line_size;
memcpy(c->manage.saved_data, line, c->manage.saved_size);
}
break;
}
offset += (pos - line) + 1;
/* Reply to a management password request */
if (*c->manage.password && passwd_request)
{
ManagementCommand(c, c->manage.password, NULL, regular);
SecureZeroMemory(c->manage.password, sizeof(c->manage.password));
continue;
}
if (!*c->manage.password && passwd_request)
{
/* either we don't have a password or we used it and didn't match */
MsgToEventLog(EVENTLOG_WARNING_TYPE, L"%ls: management password mismatch",
c->config_name);
c->state = disconnecting;
CloseManagement(c);
rtmsg_handler[stop_](c, "");
continue;
}
/* Handle regular management interface output */
line[pos - line - 1] = '\0';
if (line[0] == '>')
{
/* Real time notifications */
pos = line + 1;
if (strncmp(pos, "LOG:", 4) == 0)
{
if (rtmsg_handler[log_])
{
rtmsg_handler[log_](c, pos + 4);
}
}
else if (strncmp(pos, "STATE:", 6) == 0)
{
if (rtmsg_handler[state_])
{
rtmsg_handler[state_](c, pos + 6);
}
}
else if (strncmp(pos, "HOLD:", 5) == 0)
{
if (rtmsg_handler[hold_])
{
rtmsg_handler[hold_](c, pos + 5);
}
}
else if (strncmp(pos, "PASSWORD:", 9) == 0)
{
if (rtmsg_handler[password_])
{
rtmsg_handler[password_](c, pos + 9);
}
}
else if (strncmp(pos, "PROXY:", 6) == 0)
{
if (rtmsg_handler[proxy_])
{
rtmsg_handler[proxy_](c, pos + 6);
}
}
else if (strncmp(pos, "INFO:", 5) == 0)
{
/* delay until management interface accepts input */
/* use real sleep here, since WM_MANAGEMENT might arrive before management is ready */
Sleep(100);
c->manage.connected = 2;
if (rtmsg_handler[ready_])
{
rtmsg_handler[ready_](c, pos + 5);
}
}
else if (strncmp(pos, "NEED-OK:", 8) == 0)
{
if (rtmsg_handler[needok_])
{
rtmsg_handler[needok_](c, pos + 8);
}
}
else if (strncmp(pos, "NEED-STR:", 9) == 0)
{
if (rtmsg_handler[needstr_])
{
rtmsg_handler[needstr_](c, pos + 9);
}
}
else if (strncmp(pos, "ECHO:", 5) == 0)
{
if (rtmsg_handler[echo_])
{
rtmsg_handler[echo_](c, pos + 5);
}
}
else if (strncmp(pos, "BYTECOUNT:", 10) == 0)
{
if (rtmsg_handler[bytecount_])
{
rtmsg_handler[bytecount_](c, pos + 10);
}
}
else if (strncmp(pos, "INFOMSG:", 8) == 0)
{
if (rtmsg_handler[infomsg_])
{
rtmsg_handler[infomsg_](c, pos + 8);
}
}
else if (strncmp(pos, "PKCS11ID", 8) == 0
&& c->manage.cmd_queue)
{
/* This is not a real-time message, but unfortunately implemented
* in the core as one. Work around by handling the response here.
*/
mgmt_cmd_t *cmd = c->manage.cmd_queue;
if (cmd->handler)
{
cmd->handler(c, line);
}
UnqueueCommand(c);
}
}
else if (c->manage.cmd_queue)
{
/* Response to commands */
mgmt_cmd_t *cmd = c->manage.cmd_queue;
if (strncmp(line, "SUCCESS:", 8) == 0)
{
if (cmd->handler)
{
cmd->handler(c, line + 9);
}
UnqueueCommand(c);
}
else if (strncmp(line, "ERROR:", 6) == 0)
{
/* Response sent to management is not processed. Log an error in status window */
char buf[256];
_snprintf_0(buf, "%lld,N,Previous command sent to management failed: %s",
(long long)time(NULL), line)
rtmsg_handler[log_](c, buf);
if (cmd->handler)
{
cmd->handler(c, NULL);
}
UnqueueCommand(c);
}
else if (strcmp(line, "END") == 0)
{
UnqueueCommand(c);
}
else if (cmd->handler)
{
cmd->handler(c, line);
}
}
}
free(data);
break;
case FD_WRITE:
SendCommand(c);
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);
c->manage.saved_data = NULL;
c->manage.saved_size = 0;
}
closesocket(c->manage.sk);
c->manage.sk = INVALID_SOCKET;
c->manage.connected = 0;
while (UnqueueCommand(c))
{
}
WSACleanup();
}
}