diff --git a/Makefile.am b/Makefile.am index 8301087..672936e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,6 +92,7 @@ openvpn_gui_SOURCES = \ chartable.h \ save_pass.c save_pass.h \ env_set.c env_set.h \ + echo.c echo.h \ openvpn-gui-res.h openvpn_gui_LDFLAGS = -mwindows diff --git a/echo.c b/echo.c new file mode 100644 index 0000000..63a6394 --- /dev/null +++ b/echo.c @@ -0,0 +1,247 @@ +/* + * OpenVPN-GUI -- A Windows GUI for OpenVPN. + * + * Copyright (C) 2017 Selva Nair + * + * 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 "main.h" +#include "options.h" +#include "misc.h" +#include "openvpn.h" +#include "echo.h" +#include "tray.h" + +/* echo msg types */ +#define ECHO_MSG_WINDOW (1) +#define ECHO_MSG_NOTIFY (2) + +struct echo_msg_history { + struct echo_msg_fp fp; + struct echo_msg_history *next; +}; + +/* We use a global message window for all messages + */ +static HWND echo_msg_window; + +/* Forward declarations */ +static void +AddMessageBoxText(HWND hwnd, const wchar_t *text, const wchar_t *title, BOOL show); + +void +echo_msg_init(void) +{ + /* TODO: create a message box and save handle in echo_msg_window */ + return; +} + +/* compute a digest of the message and add it to the msg struct */ +static void +echo_msg_add_fp(struct echo_msg *msg, time_t timestamp) +{ + msg->fp.timestamp = timestamp; + /* digest not implemented */ + return; +} + +/* Add message to history -- update if already present */ +static void +echo_msg_save(struct echo_msg *msg) +{ + /* Not implemented */ + return; +} + +/* persist echo msg history to the registry */ +void +echo_msg_persist(connection_t *c) +{ + /* Not implemented */ + return; +} + +/* load echo msg history from registry */ +void +echo_msg_load(connection_t *c) +{ + /* Not implemented */ + return; +} + +/* Return true if the message is same as recently shown */ +static BOOL +echo_msg_repeated(const struct echo_msg *msg) +{ + /* Not implemented */ + return false; +} + +/* Append a line of echo msg */ +static void +echo_msg_append(connection_t *c, time_t UNUSED timestamp, const char *msg, BOOL addnl) +{ + wchar_t *eol = L""; + wchar_t *wmsg = NULL; + + if (!(wmsg = Widen(msg))) + { + WriteStatusLog(c, L"GUI> ", L"Error: out of memory while processing echo msg", false); + goto out; + } + + size_t len = c->echo_msg.txtlen + wcslen(wmsg) + 1; /* including null terminator */ + if (addnl) + { + eol = L"\r\n"; + len += 2; + } + WCHAR *s = realloc(c->echo_msg.text, len*sizeof(WCHAR)); + if (!s) + { + WriteStatusLog(c, L"GUI> ", L"Error: out of memory while processing echo msg", false); + goto out; + } + swprintf(s + c->echo_msg.txtlen, len - c->echo_msg.txtlen, L"%s%s", wmsg, eol); + + s[len-1] = L'\0'; + c->echo_msg.text = s; + c->echo_msg.txtlen = len - 1; /* exclude null terminator */ + +out: + free(wmsg); + return; +} + +/* Called when echo msg-window or echo msg-notify is received */ +static void +echo_msg_display(connection_t *c, time_t timestamp, const char *title, int type) +{ + WCHAR *wtitle = Widen(title); + + if (wtitle) + { + c->echo_msg.title = wtitle; + } + else + { + WriteStatusLog(c, L"GUI> ", L"Error: out of memory converting echo message title to widechar", false); + c->echo_msg.title = L"Message from server"; + } + echo_msg_add_fp(&c->echo_msg, timestamp); /* add fingerprint: digest+timestamp */ + + /* Check whether the message is muted */ + if (c->flags & FLAG_DISABLE_ECHO_MSG || echo_msg_repeated(&c->echo_msg)) + { + return; + } + if (type == ECHO_MSG_WINDOW) + { + HWND h = echo_msg_window; + if (h) + { + AddMessageBoxText(h, c->echo_msg.text, c->echo_msg.title, true); + } + } + else /* notify */ + { + ShowTrayBalloon(c->echo_msg.title, c->echo_msg.text); + } + /* save or update history */ + echo_msg_save(&c->echo_msg); +} + +void +echo_msg_process(connection_t *c, time_t timestamp, const char *s) +{ + wchar_t errmsg[256] = L""; + + char *msg = url_decode(s); + if (!msg) + { + WriteStatusLog(c, L"GUI> ", L"Error in url_decode of echo message", false); + return; + } + + if (strbegins(msg, "msg ")) + { + echo_msg_append(c, timestamp, msg + 4, true); + } + else if (streq(msg, "msg")) /* empty msg is treated as a new line */ + { + echo_msg_append(c, timestamp, msg+3, true); + } + else if (strbegins(msg, "msg-n ")) + { + echo_msg_append(c, timestamp, msg + 6, false); + } + else if (strbegins(msg, "msg-window ")) + { + echo_msg_display(c, timestamp, msg + 11, ECHO_MSG_WINDOW); + echo_msg_clear(c, false); + } + else if (strbegins(msg, "msg-notify ")) + { + echo_msg_display(c, timestamp, msg + 11, ECHO_MSG_NOTIFY); + echo_msg_clear(c, false); + } + else + { + _sntprintf_0(errmsg, L"WARNING: Unknown ECHO directive '%hs' ignored.", msg); + WriteStatusLog(c, L"GUI> ", errmsg, false); + } + free(msg); +} + +void +echo_msg_clear(connection_t *c, BOOL clear_history) +{ + CLEAR(c->echo_msg.fp); + free(c->echo_msg.text); + free(c->echo_msg.title); + c->echo_msg.text = NULL; + c->echo_msg.txtlen = 0; + c->echo_msg.title = NULL; + + if (clear_history) + { + echo_msg_persist(c); + struct echo_msg_history *head = c->echo_msg.history; + struct echo_msg_history *next; + while (head) + { + next = head->next; + free(head); + head = next; + } + CLEAR(c->echo_msg); + } +} + +/* Add new message to the message box window and optionally show it */ +static void +AddMessageBoxText(HWND hwnd, const wchar_t *text, const wchar_t *title, BOOL show) +{ + /* Not implemented */ + return; +} diff --git a/echo.h b/echo.h new file mode 100644 index 0000000..5ade93d --- /dev/null +++ b/echo.h @@ -0,0 +1,57 @@ +/* + * OpenVPN-GUI -- A Windows GUI for OpenVPN. + * + * Copyright (C) 2017 Selva Nair + * + * 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 + */ + +#ifndef ECHO_H +#define ECHO_H + +#include + +/* data structures and methods for handling echo msg */ +#define HASHLEN 20 + +/* message finger print consists of a SHA1 hash and a timestamp */ +struct echo_msg_fp { + BYTE digest[HASHLEN]; + time_t timestamp; +}; +struct echo_msg_history; +struct echo_msg { + struct echo_msg_fp fp; /* keep this as the first element */ + wchar_t *title; + wchar_t *text; + int txtlen; + int type; + struct echo_msg_history *history; +}; + +/* init echo message -- call on program start */ +void echo_msg_init(); + +/* Process echo msg and related commands received from mgmt iterface. */ +void echo_msg_process(connection_t *c, time_t timestamp, const char *msg); + +/* Clear echo msg buffers and optionally history */ +void echo_msg_clear(connection_t *c, BOOL clear_history); + +/* Load echo msg history from the registry */ +void echo_msg_load(connection_t *c); + +#endif diff --git a/main.c b/main.c index 4cb9f5f..4545745 100644 --- a/main.c +++ b/main.c @@ -49,6 +49,7 @@ #include "manage.h" #include "misc.h" #include "save_pass.h" +#include "echo.h" #ifndef DISABLE_CHANGE_PASSWORD #include @@ -510,6 +511,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM ChangeWindowMessageFilterEx(hwnd, WM_COPYDATA, MSGFLT_ALLOW, NULL); #endif + echo_msg_init(); + CreatePopupMenus(); /* Create popup menus */ ShowTrayIcon(); if (o.service_only) diff --git a/misc.c b/misc.c index d5594ae..8597f1e 100644 --- a/misc.c +++ b/misc.c @@ -488,3 +488,35 @@ CloseHandleEx(LPHANDLE handle) *handle = INVALID_HANDLE_VALUE; } } + +/* + * Decode url encoded characters in buffer src and + * return the result in a newly allocated buffer. The + * caller should free the returned pointer. Returns + * NULL on memory allocation error. + */ +char * +url_decode(const char *src) +{ + const char *s = src; + char *out = malloc(strlen(src) + 1); /* output is guaranteed to be not longer than src */ + char *o; + + if (!out) + return NULL; + + for (o = out; *s; o++) + { + unsigned int c = *s++; + if (c == '%' && isxdigit(s[0]) && isxdigit(s[1])) + { + sscanf(s, "%2x", &c); + s += 2; + } + /* We passthough all other chars including % not followed by 2 hex digits */ + *o = (char)c; + } + *o = '\0'; + + return out; +} diff --git a/misc.h b/misc.h index 6eb3bb0..cbf0a24 100644 --- a/misc.h +++ b/misc.h @@ -48,4 +48,9 @@ void CloseSemaphore(HANDLE sem); /* Close a handle if not null or invalid */ void CloseHandleEx(LPHANDLE h); +/* Decode url encoded charcters in src and return the result as a newly + * allocated string. Returns NULL on error. + */ +char *url_decode(const char *src); + #endif diff --git a/openvpn-gui.vcxproj b/openvpn-gui.vcxproj index d11534f..e50b664 100644 --- a/openvpn-gui.vcxproj +++ b/openvpn-gui.vcxproj @@ -137,6 +137,7 @@ + @@ -158,6 +159,7 @@ + diff --git a/openvpn-gui.vcxproj.filters b/openvpn-gui.vcxproj.filters index c086ff1..aa7b8f8 100644 --- a/openvpn-gui.vcxproj.filters +++ b/openvpn-gui.vcxproj.filters @@ -18,6 +18,9 @@ Source Files + + Source Files + Source Files @@ -74,6 +77,9 @@ Header Files + + Header Files + Header Files diff --git a/openvpn.c b/openvpn.c index 51a0faf..a695a4c 100644 --- a/openvpn.c +++ b/openvpn.c @@ -55,6 +55,7 @@ #include "access.h" #include "save_pass.h" #include "env_set.h" +#include "echo.h" extern options_t o; @@ -324,6 +325,7 @@ OnStateChange(connection_t *c, char *data) c->failed_psw_attempts++; } + echo_msg_clear(c, false); /* do not clear history */ // We change the state to reconnecting only if there was a prior successful connection. if (c->state == connected) { @@ -991,6 +993,10 @@ OnEcho(connection_t *c, char *msg) { process_setenv(c, timestamp, msg); } + else if (strbegins(msg, "msg")) + { + echo_msg_process(c, timestamp, msg); + } else { wchar_t errmsg[256]; @@ -1580,6 +1586,7 @@ Cleanup (connection_t *c) free_dynamic_cr (c); env_item_del_all(c->es); c->es = NULL; + echo_msg_clear(c, true); /* clear history */ if (c->hProcess) CloseHandle (c->hProcess); @@ -1825,6 +1832,9 @@ ThreadOpenVPNStatus(void *p) if (o.silent_connection == 0) ShowWindow(c->hwndStatus, SW_SHOW); + /* Load echo msg histroy from registry */ + echo_msg_load(c); + /* Run the message loop for the status window */ while (WM_QUIT != msg.message) { diff --git a/options.h b/options.h index 78fa574..e994471 100644 --- a/options.h +++ b/options.h @@ -33,6 +33,7 @@ typedef struct connection connection_t; #include #include "manage.h" +#include "echo.h" #define MAX_NAME (UNLEN + 1) @@ -87,6 +88,7 @@ typedef struct { #define FLAG_SAVE_KEY_PASS (1<<4) #define FLAG_SAVE_AUTH_PASS (1<<5) #define FLAG_DISABLE_SAVE_PASS (1<<6) +#define FLAG_DISABLE_ECHO_MSG (1<<7) #define CONFIG_VIEW_AUTO (0) #define CONFIG_VIEW_FLAT (1) @@ -155,6 +157,7 @@ struct connection { unsigned long long int bytes_in; unsigned long long int bytes_out; struct env_item *es; /* Pointer to the head of config-specific env variables list */ + struct echo_msg echo_msg; /* Message echo-ed from server or client config and related data */ }; /* All options used within OpenVPN GUI */