/* * OpenVPN-GUI -- A Windows GUI for OpenVPN. * * Copyright (C) 2010 Heiko Hund * * 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 #endif #include #include #include #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(); } }