summaryrefslogtreecommitdiffstats
path: root/src/remmina_info.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/remmina_info.c')
-rw-r--r--src/remmina_info.c1328
1 files changed, 1328 insertions, 0 deletions
diff --git a/src/remmina_info.c b/src/remmina_info.c
new file mode 100644
index 0000000..704ef3a
--- /dev/null
+++ b/src/remmina_info.c
@@ -0,0 +1,1328 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ * Copyright (C) 2023-2024 Hiroyuki Tanaka, Sunil Bhat
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+/**
+ * @file remmina_info.c
+ * @brief Remmina usage statistics module.
+ * @author Antenore Gatta and Giovanni Panozzo
+ * @date 12 Feb 2018
+ *
+ * Since October 29, 2021 data is not collected nor sent to remmina.org anymore. All the
+ * code intended to send data has been removed. The following documentation
+ * has to be kept for those with versions of Remmina older than 1.4.22.
+ *
+ * When Remmina starts asks the user to share some usage statistics
+ * with the Remmina developers. As per the opt-in model
+ * (https://en.wikipedia.org/wiki/Opt-in_email), without the consent of the user,
+ * no data will be collected.
+ * Additionally a user can ask, at any moment, that any data linked to their
+ * profile to be deleted, and can change the Remmina settings to stop
+ * collecting and sharing usage statistics.
+ *
+ * All the data are encrypted at client side using RSA, through the OpenSSL
+ * libraries, and decrypted offline to maximize security.
+ *
+ * The following example show which kind of data are collected.
+ *
+ * @code
+ * {
+ *
+ * "UID": "P0M20TXN03DWF4-9a1e6da2ad"
+ * "REMMINAVERSION": {
+ * "version": "1.2.0-rcgit-26"
+ * "git_revision": "9c5c4805"
+ * "snap_build": 0
+ * }
+ * "SYSTEM": {
+ * "kernel_name": "Linux"
+ * "kernel_release": "4.14.11-200.fc26.x86_64"
+ * "kernel_arch": "x86_64"
+ * "lsb_distributor": "Fedora"
+ * "lsb_distro_description": "Fedora release 26 (Twenty Six)"
+ * "lsb_distro_release": "26"
+ * "lsb_distro_codename": "TwentySix"
+ * "etc_release": "Fedora release 26 (Twenty Six)"
+ * }
+ * "GTKVERSION": {
+ * "major": 3
+ * "minor": 22
+ * "micro": 21
+ * }
+ * "GTKBACKEND": "X11"
+ * "WINDOWMANAGER": {
+ * "window_manager": "GNOME i3-gnome"
+ * }
+ * "APPINDICATOR": {
+ * "appindicator_supported": 0
+ * "appindicator_compiled": 1
+ * "icon_is_active": 1
+ * "appindicator_type": "AppIndicator on GtkStatusIcon/xembed"
+ * }
+ * "PROFILES": {
+ * "profile_count": 457
+ * "SSH": 431
+ * "NX": 1
+ * "RDP": 7
+ * "TERMINAL": 2
+ * "X2GO": 5
+ * "SFTP": 4
+ * "PYTHON_SIMPLE": 4
+ * "SPICE": 3
+ * "DATE_SSH": "20180209"
+ * "DATE_NX": ""
+ * "DATE_RDP": "20180208"
+ * "DATE_TERMINAL": ""
+ * "DATE_X2GO": ""
+ * "DATE_SFTP": ""
+ * "DATE_PYTHON_SIMPLE": ""
+ * "DATE_SPICE": ""
+ * }
+ * "ENVIRONMENT": {
+ * "language": "en_US.utf8"
+ * }
+ * "ACTIVESECRETPLUGIN": {
+ * "plugin_name": "kwallet"
+ * }
+ * "HASPRIMARYPASSWORD": {
+ * "primary_password_status": "OFF"
+ * }
+ *
+ * }
+ * @endcode
+ *
+ * All of these data are solely transmitted to understand:
+ * - On which type of system Remmina is used
+ * - Operating System
+ * - Architecture (32/64bit)
+ * - Linux distributor or OS vendor
+ * - Desktop Environment type.
+ * - Main library versions installed on the system in use by Remmina.
+ * - Protocols used
+ * - Last time each protocol has been used (globally).
+ *
+ * @see https://www.remmina.org for more info.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+
+#include "remmina.h"
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_icon.h"
+#include "remmina_log.h"
+#include "remmina_pref.h"
+#include "remmina_curl_connector.h"
+#include "remmina_sysinfo.h"
+#include "remmina_utils.h"
+#include "remmina_scheduler.h"
+#include "remmina_public.h"
+#include "remmina_main.h"
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_plugin_manager.h"
+
+#ifdef GDK_WINDOWING_WAYLAND
+ #include <gdk/gdkwayland.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+ #include <gdk/gdkx.h>
+#endif
+#include "remmina_info.h"
+
+#define MAX_ENV_LEN 10000
+
+gboolean info_disable_stats = 1;
+gboolean info_disable_news = 1;
+gboolean info_disable_tip = 1;
+
+
+struct ProfilesData {
+ GHashTable *proto_count;
+ GHashTable *proto_date;
+ const gchar *protocol; /** Key in the proto_count hash table.*/
+ const gchar *pdatestr; /** Date in string format in the proto_date hash table. */
+ gint pcount;
+ gchar datestr;
+};
+
+#if !JSON_CHECK_VERSION(1, 2, 0)
+ #define json_node_unref(x) json_node_free(x)
+#endif
+
+/* Timers */
+#define INFO_PERIODIC_CHECK_1ST_MS 1000
+#define INFO_PERIODIC_CHECK_INTERVAL_MS 86400000
+
+#define PERIODIC_UPLOAD_URL "https://info.remmina.org/info/upload_stats"
+#define INFO_REQUEST_URL "https://info.remmina.org/info/handshake"
+
+
+static RemminaInfoDialog *remmina_info_dialog;
+#define GET_OBJ(object_name) gtk_builder_get_object(remmina_info_dialog->builder, object_name)
+
+typedef struct {
+ gboolean send_stats;
+ JsonNode *statsroot;
+} sc_tdata;
+
+JsonNode *remmina_info_stats_get_uid()
+{
+ TRACE_CALL(__func__);
+ JsonNode *r;
+ GChecksum *chs;
+ const gchar *uname, *hname;
+ const gchar *uid_suffix;
+ gchar *uid_prefix;
+ gchar *uid;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ if (remmina_pref.info_uid_prefix == NULL || remmina_pref.info_uid_prefix[0] == 0) {
+ /* Generate a new UUID_PREFIX for this installation */
+ uid_prefix = remmina_gen_random_uuid();
+ if (remmina_pref.info_uid_prefix) {
+ g_free(remmina_pref.info_uid_prefix);
+ }
+ remmina_pref.info_uid_prefix = uid_prefix;
+ remmina_pref_save();
+ }
+
+ uname = g_get_user_name();
+ hname = g_get_host_name();
+ chs = g_checksum_new(G_CHECKSUM_SHA256);
+ g_checksum_update(chs, (const guchar *)uname, strlen(uname));
+ g_checksum_update(chs, (const guchar *)hname, strlen(hname));
+ uid_suffix = g_checksum_get_string(chs);
+
+ uid = g_strdup_printf("%s-%.10s", remmina_pref.info_uid_prefix, uid_suffix);
+ g_checksum_free(chs);
+
+ r = json_node_alloc();
+ json_node_init_string(r, uid);
+
+ g_free(uid);
+
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_os_info()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ gchar *kernel_name;
+ gchar *kernel_release;
+ gchar *kernel_arch;
+ gchar *id;
+ gchar *description;
+ GHashTable *etc_release;
+ gchar *release;
+ gchar *codename;
+ GHashTableIter iter;
+ gchar *key, *value;
+ gchar *mage;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "kernel_name");
+ kernel_name = remmina_utils_get_kernel_name();
+ if (!kernel_name || kernel_name[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_name);
+ }
+ g_free(kernel_name);
+
+ json_builder_set_member_name(b, "kernel_release");
+ kernel_release = remmina_utils_get_kernel_release();
+ if (!kernel_release || kernel_release[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_release);
+ }
+ g_free(kernel_release);
+
+ json_builder_set_member_name(b, "kernel_arch");
+ kernel_arch = remmina_utils_get_kernel_arch();
+ if (!kernel_arch || kernel_arch[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_arch);
+ }
+ g_free(kernel_arch);
+
+ json_builder_set_member_name(b, "lsb_distributor");
+ id = remmina_utils_get_lsb_id();
+ if (!id || id[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, id);
+ }
+ g_free(id);
+
+ json_builder_set_member_name(b, "lsb_distro_description");
+ description = remmina_utils_get_lsb_description();
+ if (!description || description[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, description);
+ }
+ g_free(description);
+
+ json_builder_set_member_name(b, "lsb_distro_release");
+ release = remmina_utils_get_lsb_release();
+ if (!release || release[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, release);
+ }
+ g_free(release);
+
+ json_builder_set_member_name(b, "lsb_distro_codename");
+ codename = remmina_utils_get_lsb_codename();
+ if (!codename || codename[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, codename);
+ }
+ g_free(codename);
+
+ etc_release = remmina_utils_get_etc_release();
+ json_builder_set_member_name(b, "etc_release");
+ if (etc_release) {
+ json_builder_begin_object(b);
+ g_hash_table_iter_init (&iter, etc_release);
+ while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&value)) {
+ json_builder_set_member_name(b, key);
+ json_builder_add_string_value(b, value);
+ }
+ json_builder_end_object(b);
+ g_hash_table_remove_all(etc_release);
+ g_hash_table_unref(etc_release);
+ }else {
+ json_builder_add_null_value(b);
+ }
+
+ mage = remmina_utils_get_mage();
+ json_builder_set_member_name(b, "mage");
+ json_builder_add_string_value(b, mage);
+ g_free(mage);
+
+
+ /** @todo Add other means to identify a release name/description
+ * to cover as much OS as possible, like /etc/issue
+ */
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+/**
+ * Gets the following user environment:
+ * - Gets the user’s locale (or NULL by default) corresponding to LC_ALL.
+ *
+ * @return a JSON Node structure containing the user’s environment.
+ */
+JsonNode *remmina_info_stats_get_user_env()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ gchar *language;
+ gchar **environment;
+ gchar **uenv;
+ gchar *str;
+ gsize env_len = 0;
+
+ language = remmina_utils_get_lang();
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ environment = g_get_environ();
+ uenv = environment;
+
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "language");
+ json_builder_add_string_value(b, language);
+
+ gchar *safe_ptr;
+ while (*uenv && env_len <= MAX_ENV_LEN) {
+ str = strtok_r(*uenv, "=", &safe_ptr);
+ if (str != NULL) {
+ env_len += strlen(str);
+ json_builder_set_member_name(b, str);
+ }
+ str = strtok_r(NULL, "\n", &safe_ptr);
+ if (str != NULL) {
+ env_len += strlen(str);
+ json_builder_add_string_value(b, str);
+ }
+ else{
+ json_builder_add_string_value(b, "");
+ }
+ uenv++;
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ g_strfreev(environment);
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_host() {
+
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ gchar *logical, *physical;
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "host");
+ logical = remmina_utils_get_logical();
+ if (!logical || logical[0] == '\0') {
+ json_builder_add_null_value(b);
+ }
+ else {
+ json_builder_add_string_value(b, logical);
+ }
+ g_free(logical);
+
+ json_builder_set_member_name(b, "hw");
+ physical = remmina_utils_get_link();
+ if (!physical || physical[0] == '\0') {
+ json_builder_add_null_value(b);
+ }
+ else {
+ json_builder_add_string_value(b, physical);
+ }
+ g_free(physical);
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_version()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *flatpak_info;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "version");
+ json_builder_add_string_value(b, VERSION);
+ json_builder_set_member_name(b, "git_revision");
+ json_builder_add_string_value(b, REMMINA_GIT_REVISION);
+ json_builder_set_member_name(b, "snap_build");
+#ifdef SNAP_BUILD
+ json_builder_add_int_value(b, 1);
+#else
+ json_builder_add_int_value(b, 0);
+#endif
+
+ /**
+ * Detect if Remmina is running under Flatpak
+ */
+ json_builder_set_member_name(b, "flatpak_build");
+ /* Flatpak sandbox should contain the file ${XDG_RUNTIME_DIR}/flatpak-info */
+ flatpak_info = g_build_filename(g_get_user_runtime_dir(), "flatpak-info", NULL);
+ if (g_file_test(flatpak_info, G_FILE_TEST_EXISTS)) {
+ json_builder_add_int_value(b, 1);
+ } else {
+ json_builder_add_int_value(b, 0);
+ }
+ g_free(flatpak_info);
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_gtk_version()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "major");
+ json_builder_add_int_value(b, gtk_get_major_version());
+ json_builder_set_member_name(b, "minor");
+ json_builder_add_int_value(b, gtk_get_minor_version());
+ json_builder_set_member_name(b, "micro");
+ json_builder_add_int_value(b, gtk_get_micro_version());
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_gtk_backend()
+{
+ TRACE_CALL(__func__);
+ JsonNode *r;
+ GdkDisplay *disp;
+ gchar *bkend;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ disp = gdk_display_get_default();
+
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY(disp)) {
+ bkend = "Wayland";
+ }else
+#endif
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(disp)) {
+ bkend = "X11";
+ } else
+#endif
+ bkend = "n/a";
+
+ r = json_node_alloc();
+ json_node_init_string(r, bkend);
+
+ return r;
+
+}
+
+JsonNode *remmina_info_stats_get_wm_name()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *wmver;
+ gchar *wmname;
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "window_manager");
+
+ /** We try to get the GNOME Shell version */
+ wmver = remmina_sysinfo_get_gnome_shell_version();
+ if (!wmver || wmver[0] == '\0') {
+ }else {
+ json_builder_add_string_value(b, "GNOME Shell");
+ json_builder_set_member_name(b, "gnome_shell_ver");
+ json_builder_add_string_value(b, wmver);
+ goto end;
+ }
+ g_free(wmver);
+
+ wmname = remmina_sysinfo_get_wm_name();
+ if (!wmname || wmname[0] == '\0') {
+ json_builder_add_string_value(b, "n/a");
+ }else {
+ json_builder_add_string_value(b, wmname);
+ }
+ g_free(wmname);
+
+ end:
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_indicator()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gboolean sni; /** Support for StatusNotifier or AppIndicator */
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "appindicator_supported");
+ sni = remmina_sysinfo_is_appindicator_available();
+ if (sni) {
+ /** StatusNotifier/Appindicator supported by desktop */
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "appindicator_compiled");
+#ifdef HAVE_LIBAPPINDICATOR
+ /** libappindicator is compiled in remmina. */
+ json_builder_add_int_value(b, 1);
+#else
+ /** Remmina not compiled with -DWITH_APPINDICATOR=on */
+ json_builder_add_int_value(b, 0);
+#endif
+ }
+ else{
+ /** StatusNotifier/Appindicator NOT supported by desktop */
+ json_builder_add_int_value(b, 0);
+ }
+
+ json_builder_set_member_name(b, "icon_is_active");
+ if (remmina_icon_is_available()) {
+ /** Remmina icon is active */
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "appindicator_type");
+#ifdef HAVE_LIBAPPINDICATOR
+ /** libappindicator fallback to GtkStatusIcon/xembed"); */
+ json_builder_add_string_value(b, "AppIndicator on GtkStatusIcon/xembed");
+#else
+ /** Remmina fallback to GtkStatusIcon/xembed */
+ json_builder_add_string_value(b, "Remmina icon on GtkStatusIcon/xembed");
+#endif
+ }else {
+ /** Remmina icon is NOT active */
+ json_builder_add_int_value(b, 0);
+ }
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+/**
+ * Given a remmina file, fills a structure containing profiles keys/value tuples.
+ *
+ * This is used as a callback function with remmina_file_manager_iterate.
+ * @todo Move this in a separate file.
+ */
+static void remmina_info_profiles_get_data(RemminaFile *remminafile, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ gint count = 0;
+ gpointer pcount, kpo;
+ gpointer pdate;
+ gchar *hday, *hmonth, *hyear;
+ gchar *pday, *pmonth, *pyear;
+
+ GDateTime *prof_gdate; /** Source date -> from profile */
+ GDateTime *pdata_gdate; /** Destination date -> The date in the pdata structure */
+
+ struct ProfilesData* pdata;
+ pdata = (struct ProfilesData*)user_data;
+
+ pdata->protocol = remmina_file_get_string(remminafile, "protocol");
+ //pdata->pdatestr = remmina_file_get_string(remminafile, "last_success");
+ const gchar *last_success = remmina_file_get_string(remminafile, "last_success");
+
+ prof_gdate = pdata_gdate = NULL;
+ if (last_success && last_success[0] != '\0' && strlen(last_success) >= 6) {
+ pyear = g_strdup_printf("%.4s", last_success);
+ pmonth = g_strdup_printf("%.2s", last_success + 4);
+ pday = g_strdup_printf("%.2s", last_success + 6);
+ prof_gdate = g_date_time_new_local(
+ atoi(pyear),
+ atoi(pmonth),
+ atoi(pday), 0, 0, 0);
+ g_free(pyear);
+ g_free(pmonth);
+ g_free(pday);
+ }
+
+
+ if (pdata->protocol && pdata->protocol[0] != '\0') {
+ if (g_hash_table_lookup_extended(pdata->proto_count, pdata->protocol, &kpo, &pcount)) {
+ count = GPOINTER_TO_INT(pcount) + 1;
+ }else {
+ count = 1;
+ g_hash_table_insert(pdata->proto_count, g_strdup(pdata->protocol), GINT_TO_POINTER(count));
+ }
+ g_hash_table_replace(pdata->proto_count, g_strdup(pdata->protocol), GINT_TO_POINTER(count));
+ pdate = NULL;
+ if (g_hash_table_lookup_extended(pdata->proto_date, pdata->protocol, NULL, &pdate)) {
+
+ pdata_gdate = NULL;
+ if (pdate && strlen(pdate) >= 6) {
+ pdata->pdatestr = g_strdup(pdate);
+ hyear = g_strdup_printf("%.4s", (char*)pdate);
+ hmonth = g_strdup_printf("%.2s", (char*)pdate + 4);
+ hday = g_strdup_printf("%.2s", (char*)pdate + 6);
+ pdata_gdate = g_date_time_new_local(
+ atoi(hyear),
+ atoi(hmonth),
+ atoi(hday), 0, 0, 0);
+ g_free(hyear);
+ g_free(hmonth);
+ g_free(hday);
+ }
+
+ /** When both date in the hash and in the profile are valid we compare the date */
+ if (prof_gdate != NULL && pdata_gdate != NULL ) {
+ gint res = g_date_time_compare( pdata_gdate, prof_gdate );
+ /** If the date in the hash less than the date in the profile, we take the latter */
+ if (res < 0 ) {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(last_success));
+ } else {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }
+
+ }
+ /** If the date in the profile is NOT valid and the date in the hash is valid we keep the latter */
+ if (prof_gdate == NULL && pdata_gdate != NULL) {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }
+
+ /** If the date in the hash is NOT valid and the date in the profile is valid we keep the latter */
+ if (prof_gdate != NULL && pdata_gdate == NULL) {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(last_success));
+ }
+ /** If both date are NULL, we insert NULL for that protocol */
+ if ((prof_gdate == NULL && pdata_gdate == NULL) && pdata->pdatestr) {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), NULL);
+ }
+ } else {
+ /** If there is not the protocol in the hash, we add it */
+ /** If the date in the profile is not NULL we use it */
+ if (pdata->pdatestr) {
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }else {
+ /** Otherwise we set it to NULL */
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), NULL);
+ }
+ }
+ }
+ if (pdata_gdate)
+ g_date_time_unref(pdata_gdate);
+ if (prof_gdate)
+ g_date_time_unref(prof_gdate);
+}
+
+/**
+ * Add a JSON member profile_count with a child for each protocol used by the user.
+ * Count how many profiles are in use and for each protocol in use counts of how many
+ * profiles that uses such protocol.
+ *
+ * The data can be expressed as follows:
+ *
+ * | PROTO | PROF COUNT |
+ * |:-------|-----------:|
+ * | RDP | 2560 |
+ * | SPICE | 334 |
+ * | SSH | 1540 |
+ * | VNC | 2 |
+ *
+ * | PROTO | LAST USED |
+ * |:-------|----------:|
+ * | RDP | 20180129 |
+ * | SPICE | 20171122 |
+ * | SSH | 20180111 |
+ *
+ * @return a JSON Node structure containing the protocol usage statistics.
+ *
+ */
+JsonNode *remmina_info_stats_get_profiles()
+{
+ TRACE_CALL(__func__);
+
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *s;
+
+ gint profiles_count;
+ GHashTableIter pcountiter, pdateiter;
+ gpointer pcountkey, pcountvalue;
+ gpointer pdatekey, pdatevalue;
+
+ struct ProfilesData *pdata;
+ pdata = g_malloc0(sizeof(struct ProfilesData));
+ if (pdata == NULL) {
+ return NULL;
+ }
+
+ b = json_builder_new();
+ if (b == NULL) {
+ g_free(pdata);
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "profile_count");
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+
+ pdata->proto_date = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, (GDestroyNotify)g_free);
+ pdata->proto_count = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, NULL);
+
+ profiles_count = remmina_file_manager_iterate(
+ (GFunc)remmina_info_profiles_get_data,
+ (gpointer)pdata);
+
+ json_builder_add_int_value(b, profiles_count);
+
+ g_hash_table_iter_init(&pcountiter, pdata->proto_count);
+ while (g_hash_table_iter_next(&pcountiter, &pcountkey, &pcountvalue)) {
+ json_builder_set_member_name(b, (gchar*)pcountkey);
+ json_builder_add_int_value(b, GPOINTER_TO_INT(pcountvalue));
+ }
+
+ g_hash_table_iter_init(&pdateiter, pdata->proto_date);
+ while (g_hash_table_iter_next(&pdateiter, &pdatekey, &pdatevalue)) {
+ s = g_strdup_printf("DATE_%s", (gchar*)pdatekey);
+ json_builder_set_member_name(b, s);
+ g_free(s);
+ json_builder_add_string_value(b, (gchar*)pdatevalue);
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+
+ g_hash_table_remove_all(pdata->proto_date);
+ g_hash_table_unref(pdata->proto_date);
+ g_hash_table_remove_all(pdata->proto_count);
+ g_hash_table_unref(pdata->proto_count);
+
+ g_free(pdata);
+
+ return r;
+}
+
+/**
+ * Add a JSON member ACTIVESECRETPLUGIN which shows the current secret plugin in use by Remmina.
+ *
+ * @return a JSON Node structure containing the secret plugin in use
+ *
+ */
+JsonNode *remmina_info_stats_get_secret_plugin()
+{
+ TRACE_CALL(__func__);
+
+ JsonBuilder *b;
+ JsonNode *r;
+ RemminaSecretPlugin *secret_plugin;
+ secret_plugin = remmina_plugin_manager_get_secret_plugin();
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ if (secret_plugin && secret_plugin->is_service_available) {
+ json_builder_set_member_name(b, "plugin_name");
+ json_builder_add_string_value(b, secret_plugin->name);
+ }
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+
+ return r;
+}
+
+/**
+ * Add a JSON member HASPRIMARYPASSWORD which shows the status of the master password.
+ *
+ * @return a JSON Node structure containing the status of the primary password
+ *
+ */
+JsonNode *remmina_info_stats_get_primary_password_status()
+{
+ TRACE_CALL(__func__);
+
+ JsonBuilder *b;
+ JsonNode *r;
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "primary_password_status");
+ if (remmina_pref_get_boolean("use_primary_password")) {
+ json_builder_add_string_value(b, "ON");
+ } else {
+ json_builder_add_string_value(b, "OFF");
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+
+ return r;
+}
+
+/**
+ * Add a json member KIOSK which shows the status of the kiosk.
+ *
+ * @return a JSON Node structure containing the status of the primary password
+ *
+ */
+JsonNode *remmina_info_stats_get_kiosk_mode()
+{
+ TRACE_CALL(__func__);
+
+ JsonBuilder *b;
+ JsonNode *r;
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "kiosk_status");
+ if (!kioskmode && kioskmode == FALSE) {
+ json_builder_add_string_value(b, "OFF");
+ }else {
+ json_builder_add_string_value(b, "ON");
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+
+ return r;
+}
+
+JsonNode *remmina_info_stats_get_python()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *version;
+
+ version = remmina_utils_get_python();
+ b = json_builder_new();
+ if(b != NULL)
+ {
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "version");
+ if (!version || version[0] == '\0') {
+ json_builder_add_null_value(b);
+ } else {
+ json_builder_add_string_value(b, version);
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * Get all statistics in JSON format to send periodically to the server.
+ * The caller should free the returned buffer with g_free()
+ * @warning This function is usually executed on a dedicated thread,
+ * not on the main thread.
+ * @return a pointer to the JSON string.
+ */
+JsonNode *remmina_info_stats_get_all()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *n;
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return NULL;
+ }
+
+ json_builder_begin_object(b);
+
+ n = remmina_info_stats_get_uid();
+ json_builder_set_member_name(b, "UID");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_version();
+ json_builder_set_member_name(b, "REMMINAVERSION");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_os_info();
+ json_builder_set_member_name(b, "SYSTEM");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_user_env();
+ json_builder_set_member_name(b, "ENVIRONMENT");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_gtk_version();
+ json_builder_set_member_name(b, "GTKVERSION");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_gtk_backend();
+ json_builder_set_member_name(b, "GTKBACKEND");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_wm_name();
+ json_builder_set_member_name(b, "WINDOWMANAGER");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_indicator();
+ json_builder_set_member_name(b, "APPINDICATOR");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_profiles();
+ json_builder_set_member_name(b, "PROFILES");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_secret_plugin();
+ json_builder_set_member_name(b, "ACTIVESECRETPLUGIN");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_primary_password_status();
+ json_builder_set_member_name(b, "HASPRIMARYPASSWORD");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_kiosk_mode();
+ json_builder_set_member_name(b, "KIOSK");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_python();
+ json_builder_set_member_name(b, "PYTHON");
+ json_builder_add_value(b, n);
+
+ n = remmina_info_stats_get_host();
+ json_builder_set_member_name(b, "HOST");
+ json_builder_add_value(b, n);
+
+ json_builder_end_object(b);
+ n = json_builder_get_root(b);
+ g_object_unref(b);
+
+ return n;
+}
+
+void remmina_info_schedule()
+{
+ sc_tdata *data;
+
+ data = g_malloc(sizeof(sc_tdata));
+ if (data == NULL) {
+ return;
+ }
+ data->send_stats = !info_disable_stats;
+
+ remmina_scheduler_setup(remmina_info_periodic_check,
+ data,
+ INFO_PERIODIC_CHECK_1ST_MS,
+ INFO_PERIODIC_CHECK_INTERVAL_MS);
+}
+
+static void remmina_info_close_clicked(GtkButton *btn, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (remmina_info_dialog->dialog) {
+ gtk_widget_destroy(GTK_WIDGET(remmina_info_dialog->dialog));
+ }
+ remmina_info_dialog->dialog = NULL;
+ g_free(remmina_info_dialog);
+ remmina_info_dialog = NULL;
+}
+
+static gboolean remmina_info_dialog_deleted(GtkButton *btn, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_destroy(GTK_WIDGET(remmina_info_dialog->dialog));
+ remmina_info_dialog->dialog = NULL;
+ g_free(remmina_info_dialog);
+ remmina_info_dialog = NULL;
+
+ return FALSE;
+}
+
+
+
+gboolean remmina_info_show_response(gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWindow * parent = remmina_main_get_window();
+ remmina_info_dialog = g_new0(RemminaInfoDialog, 1);
+ remmina_info_dialog->retval = 1;
+
+ RemminaInfoMessage *message = (RemminaInfoMessage*)user_data;
+
+ remmina_info_dialog->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_info.glade");
+ remmina_info_dialog->dialog = GTK_DIALOG(gtk_builder_get_object(remmina_info_dialog->builder, "RemminaInfoDialog"));
+
+ remmina_info_dialog->remmina_info_text_view = GTK_TEXT_VIEW(GET_OBJ("remmina_info_text_view"));
+ remmina_info_dialog->remmina_info_label = GTK_LABEL(GET_OBJ("remmina_info_label"));
+
+ remmina_info_dialog->remmina_info_button_close = GTK_BUTTON(GET_OBJ("remmina_info_button_close"));
+ gtk_widget_set_can_default(GTK_WIDGET(remmina_info_dialog->remmina_info_button_close), TRUE);
+ gtk_widget_grab_default(GTK_WIDGET(remmina_info_dialog->remmina_info_button_close));
+
+ gtk_label_set_markup(remmina_info_dialog->remmina_info_label, message->info_string);
+
+ g_signal_connect(remmina_info_dialog->remmina_info_button_close, "clicked",
+ G_CALLBACK(remmina_info_close_clicked), (gpointer)remmina_info_dialog);
+ g_signal_connect(remmina_info_dialog->dialog, "close",
+ G_CALLBACK(remmina_info_close_clicked), NULL);
+ g_signal_connect(remmina_info_dialog->dialog, "delete-event",
+ G_CALLBACK(remmina_info_dialog_deleted), NULL);
+
+ /* Connect signals */
+ gtk_builder_connect_signals(remmina_info_dialog->builder, NULL);
+
+ /* Show the modal news dialog */
+ gtk_widget_show_all(GTK_WIDGET(remmina_info_dialog->dialog));
+ gtk_window_present(GTK_WINDOW(remmina_info_dialog->dialog));
+ if (parent) {
+ gtk_window_set_transient_for(GTK_WINDOW(remmina_info_dialog->dialog), parent);
+ }
+ gtk_window_set_modal(GTK_WINDOW(remmina_info_dialog->dialog), TRUE);
+ gtk_window_set_title(GTK_WINDOW(remmina_info_dialog->dialog), message->title_string);
+ g_free(message);
+ return FALSE;
+}
+
+
+/**
+ * Post request to info server
+ *
+ * @return gboolean
+ */
+static gboolean remmina_info_stats_collector_done(gpointer data)
+{
+ JsonNode *n;
+ JsonGenerator *g;
+ gchar *unenc_s, *enc_s;
+ JsonBuilder *b;
+ JsonObject *o;
+ gchar *uid;
+ JsonNode *root;
+
+ root = (JsonNode *)data;
+ if (root == NULL) {
+ return G_SOURCE_REMOVE;
+ }
+
+ n = root;
+
+ if ((o = json_node_get_object(n)) == NULL) {
+ g_free(data);
+ return G_SOURCE_REMOVE;
+ }
+
+ uid = g_strdup(json_object_get_string_member(o, "UID"));
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ json_node_unref(n);
+ unenc_s = json_generator_to_data(g, NULL);
+ g_object_unref(g);
+
+ EVP_PKEY *remmina_pubkey = remmina_get_pubkey(RSA_KEYTYPE, remmina_RSA_PubKey_v1);
+ if (remmina_pubkey == NULL) {
+ g_free(uid);
+ g_free(unenc_s);
+ return G_SOURCE_REMOVE;
+ }
+ enc_s = remmina_rsa_encrypt_string(remmina_pubkey, unenc_s);
+ if (enc_s == NULL) {
+ g_free(uid);
+ g_free(unenc_s);
+ return G_SOURCE_REMOVE;
+ }
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return G_SOURCE_REMOVE;
+ }
+
+
+ EVP_PKEY_free(remmina_pubkey);
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "keyversion");
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "encdata");
+ json_builder_add_string_value(b, enc_s);
+ json_builder_set_member_name(b, "UID");
+ json_builder_add_string_value(b, uid);
+ json_builder_set_member_name(b, "news_enabled");
+ json_builder_add_int_value(b, TRUE ? 0 : 1);
+ json_builder_set_member_name(b, "stats_enabled");
+ json_builder_add_int_value(b, TRUE ? 0 : 1);
+ json_builder_set_member_name(b, "tip_enabled");
+ json_builder_add_int_value(b, TRUE ? 0 : 1);
+
+
+ json_builder_end_object(b);
+ n = json_builder_get_root(b);
+
+ g_free(unenc_s);
+ g_object_unref(b);
+ g_free(uid);
+ g_free(enc_s);
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ enc_s = json_generator_to_data(g, NULL);
+ g_object_unref(g);
+
+ remmina_curl_compose_message(enc_s, "POST", PERIODIC_UPLOAD_URL, NULL);
+
+ json_node_unref(n);
+
+ return G_SOURCE_REMOVE;
+}
+
+/**
+ * Collect stats and notify when collection is done
+ *
+ * @return gpointer
+ */
+gpointer remmina_info_stats_collector()
+{
+ JsonNode *n;
+
+ n = remmina_info_stats_get_all();
+
+ /* Stats collecting is done. Notify main thread calling
+ * remmina_info_stats_collector_done() */
+ g_idle_add(remmina_info_stats_collector_done, n);
+
+ return NULL;
+}
+
+void remmina_info_request(gpointer data)
+{
+ // send initial handshake here, in a callback decide how to handle response
+
+ JsonNode *n;
+ JsonGenerator *g;
+ gchar *enc_s;
+ JsonBuilder *b;
+ const gchar *uid = "000000";
+
+ n = remmina_info_stats_get_uid();
+ if (n != NULL){
+ uid = json_node_get_string(n);
+ }
+
+ b = json_builder_new();
+ if (b == NULL) {
+ return;
+ }
+
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "keyversion");
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "UID");
+ json_builder_add_string_value(b, uid);
+ json_builder_set_member_name(b, "news_enabled");
+ json_builder_add_int_value(b, info_disable_news ? 0 : 1);
+ json_builder_set_member_name(b, "stats_enabled");
+ json_builder_add_int_value(b, info_disable_stats ? 0 : 1);
+ json_builder_set_member_name(b, "tip_enabled");
+ json_builder_add_int_value(b, info_disable_tip ? 0 : 1);
+
+
+ json_builder_end_object(b);
+ n = json_builder_get_root(b);
+
+ g_object_unref(b);
+
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ enc_s = json_generator_to_data(g, NULL);
+ g_object_unref(g);
+ remmina_curl_compose_message(enc_s, "POST", INFO_REQUEST_URL, NULL);
+ json_node_unref(n);
+}
+
+gboolean remmina_info_periodic_check(gpointer user_data)
+{
+ remmina_info_request(user_data);
+ return G_SOURCE_CONTINUE;
+}