mirror of https://github.com/OpenVPN/openvpn-gui
				
				
				
			
		
			
				
	
	
		
			516 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			516 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
|  *  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();
 | |
|     }
 | |
| }
 |