Remmina - The GTK+ Remote Desktop Client  v1.4.2
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
remmina_stats_sender.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2016-2020 Antenore Gatta, Giovanni Panozzo
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give
21  * permission to link the code of portions of this program with the
22  * OpenSSL library under certain conditions as described in each
23  * individual source file, and distribute linked combinations
24  * including the two.
25  * You must obey the GNU General Public License in all respects
26  * for all of the code used other than OpenSSL. * If you modify
27  * file(s) with this exception, you may extend this exception to your
28  * version of the file(s), but you are not obligated to do so. * If you
29  * do not wish to do so, delete this exception statement from your
30  * version. * If you delete this exception statement from all source
31  * files in the program, then also delete it here.
32  *
33  */
34 
35 #include "config.h"
36 #include <gtk/gtk.h>
37 #include <string.h>
38 #include <libsoup/soup.h>
39 #include <openssl/rsa.h>
40 #include <openssl/pem.h>
41 #include <openssl/err.h>
42 #include "remmina_scheduler.h"
44 #include "remmina_log.h"
45 #include "remmina_stats.h"
46 #include "remmina_pref.h"
47 
48 #if !JSON_CHECK_VERSION(1, 2, 0)
49  #define json_node_unref(x) json_node_free(x)
50 #endif
51 
52 /* Timers */
53 #define PERIODIC_CHECK_1ST_MS 60000
54 #define PERIODIC_CHECK_INTERVAL_MS 1200000
55 
56 #define PERIODIC_UPLOAD_INTERVAL_SEC 2678400
57 
58 #define PERIODIC_UPLOAD_URL "https://www.remmina.org/stats/upload_stats.php"
59 
60 
61 //static gint periodic_check_source;
62 //static gint periodic_check_counter;
63 
64 static char *remmina_RSA_PubKey_v1 =
65  "-----BEGIN PUBLIC KEY-----\n"
66  "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwuI8eOnDV2y9uPdhN+6Q\n"
67  "Cju8+YapN0wKlvwfy1ccQBS+4YnM7/+vzelOzLXJwWBDr/He7G5XEIzOcc9LZsRw\n"
68  "XYAoeB3+kP4OrNIVmKfxL7uijoh+79t3WpR8OOOTFDLmtk23tvdJVj+KfRpm0REK\n"
69  "BmdPHP8NpBzQElEDgXP9weHwQhPLB6MqpaJmfR4AqSumAcsukjbSaCWhqjO2rEiA\n"
70  "eXqJ0JE+PIe4WO1IBvKyYBYP3S77FEMJojkVWGVsjOUGe2VqpX02GaRajRkbqzNK\n"
71  "dGmLQt//kcCuPkiqm/qQQTZc0JJYUrmOjFJW9jODQKXHdZrSz8Xz5+v6VJ49v2TM\n"
72  "PwIDAQAB\n"
73  "-----END PUBLIC KEY-----\n";
74 
75 typedef struct {
76  gboolean show_only;
77  JsonNode *statsroot;
78 } sc_tdata;
79 
80 static void soup_callback(SoupSession *session, SoupMessage *msg, gpointer user_data)
81 {
82  TRACE_CALL(__func__);
83  gchar *s = (gchar*)user_data;
84  SoupBuffer *sb;
85  gboolean passed;
86  GTimeVal t;
87 
88  g_free(s);
89 
90  if (msg->status_code != 200) {
91  remmina_debug("HTTP status error sending stats: %d\n", msg->status_code);
92  return;
93  }
94 
95  passed = FALSE;
96  sb = soup_message_body_flatten(msg->response_body);
97  remmina_debug("STATS script response: %.40s\n", sb->data);
98  if (strncmp(sb->data, "200 ", 4) != 0) {
99  remmina_debug("STATS http upload error from server side script: %s\n", sb->data);
100  } else {
101  passed = TRUE;
102  }
103  soup_buffer_free(sb);
104 
105  if (passed) {
106  g_get_current_time(&t);
109  }
110 
111 }
112 
113 static gchar *rsa_encrypt_string(RSA *pubKey, const char *instr)
114 {
115  TRACE_CALL(__func__);
116  /* Calls RSA_public_encrypt multiple times to encrypt instr.
117  * At the end, base64 encode the resulting buffer
118  * Return a buffer ptr. Use g_free() to deallocate it */
119 
120  int rsaLen = RSA_size(pubKey);
121  int inLen = strlen(instr);
122  int remaining, r;
123  int blksz, maxblksz;
124  int ebufSize;
125  unsigned char *ebuf, *outptr;
126  gchar *enc;
127 
128  maxblksz = rsaLen - 12;
129  ebufSize = (((inLen - 1) / maxblksz) + 1) * rsaLen;
130  ebuf = g_malloc(ebufSize);
131  outptr = ebuf;
132  remaining = strlen(instr);
133 
134  while(remaining > 0) {
135  blksz = remaining > maxblksz ? maxblksz : remaining;
136  r = RSA_public_encrypt(blksz,
137  (const unsigned char *)instr,
138  outptr,
139  pubKey, RSA_PKCS1_PADDING); /* Our poor JS libraries only supports RSA_PKCS1_PADDING */
140  if (r == -1 ) {
141  unsigned long e;
142  ERR_load_crypto_strings();
143  e = ERR_get_error();
144  g_print("Error RSA_public_encrypt(): %s - func: %s - reason: %s\n", ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e));
145  g_free(ebuf);
146  ERR_free_strings();
147  return NULL;
148  }
149  instr += blksz;
150  remaining -= blksz;
151  outptr += r;
152  }
153 
154  enc = g_base64_encode(ebuf, ebufSize);
155  g_free(ebuf);
156 
157  return enc;
158 
159 
160 }
161 
162 static gboolean remmina_stats_collector_done(gpointer data)
163 {
164  TRACE_CALL(__func__);
165  JsonNode *n;
166  JsonGenerator *g;
167  gchar *unenc_s, *enc_s;
168  SoupSession *ss;
169  SoupMessage *msg;
170  JsonBuilder *b;
171  JsonObject *o;
172  BIO *pkbio;
173  RSA *pubkey;
174  gchar *uid;
175  sc_tdata *sctdata;
176 
177  sctdata = (sc_tdata *)data;
178  if (sctdata == NULL)
179  return G_SOURCE_REMOVE;
180 
181  n = sctdata->statsroot;
182  if (n == NULL) {
183  g_free(data);
184  return G_SOURCE_REMOVE;
185  }
186 
187  if ((o = json_node_get_object(n)) == NULL) {
188  g_free(data);
189  return G_SOURCE_REMOVE;
190  }
191 
192  uid = g_strdup(json_object_get_string_member(o, "UID"));
193 
194  g = json_generator_new();
195  json_generator_set_root(g, n);
196  json_node_unref(n);
197  unenc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
198  remmina_debug("STATS upload: JSON data%s\n", unenc_s);
199  g_object_unref(g);
200 
201  /* Now encrypt "s" with remminastats public key */
202 
203  pkbio = BIO_new_mem_buf(remmina_RSA_PubKey_v1, -1);
204  pubkey = PEM_read_bio_RSA_PUBKEY(pkbio, NULL, NULL, NULL);
205  if (pubkey == NULL) {
206  ERR_load_crypto_strings();
207  unsigned long e;
208  e = ERR_get_error();
209  g_print("Failure in PEM_read_bio_RSAPublicKey: %s - func: %s - reason: %s\n", ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e));
210  g_print("%s\n", ERR_error_string( e, NULL ));
211  BIO_free(pkbio);
212  g_free(unenc_s);
213  ERR_free_strings();
214  g_free(data);
215  g_free(uid);
216  return G_SOURCE_REMOVE;
217  }
218 
219  enc_s = rsa_encrypt_string(pubkey, unenc_s);
220 
221  g_free(unenc_s);
222  BIO_free(pkbio);
223 
224 
225  /* Create new json encrypted object */
226 
227  b = json_builder_new();
228  json_builder_begin_object(b);
229  json_builder_set_member_name(b, "keyversion");
230  json_builder_add_int_value(b, 1);
231  json_builder_set_member_name(b, "encdata");
232  json_builder_add_string_value(b, enc_s);
233  json_builder_set_member_name(b, "UID");
234  json_builder_add_string_value(b, uid);
235  json_builder_end_object(b);
236  n = json_builder_get_root(b);
237  g_object_unref(b);
238 
239  g_free(uid);
240  g_free(enc_s);
241 
242  if (!sctdata->show_only) {
243 
244  g = json_generator_new();
245  json_generator_set_root(g, n);
246  enc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
247  g_object_unref(g);
248 
249  ss = soup_session_new();
250  msg = soup_message_new("POST", PERIODIC_UPLOAD_URL);
251  soup_message_set_request(msg, "application/json",
252  SOUP_MEMORY_COPY, enc_s, strlen(enc_s));
253  soup_session_queue_message(ss, msg, soup_callback, enc_s);
254 
255  remmina_debug("STATS upload: Starting upload to url %s\n", PERIODIC_UPLOAD_URL);
256  }
257 
258  json_node_unref(n);
259  g_free(data);
260 
261  return G_SOURCE_REMOVE;
262 }
263 
264 
265 static gpointer remmina_stats_collector(gpointer data)
266 {
267  TRACE_CALL(__func__);
268  JsonNode *n;
269  sc_tdata *sctdata;
270 
271  sctdata = (sc_tdata *)data;
272  n = remmina_stats_get_all();
273 
274  /* stats collecting is done. Notify main thread calling
275  * remmina_stats_collector_done() */
276  sctdata->statsroot = n;
277  g_idle_add(remmina_stats_collector_done, sctdata);
278 
279  return NULL;
280 }
281 
282 void remmina_stats_sender_send(gboolean show_only)
283 {
284  TRACE_CALL(__func__);
285 
286  sc_tdata *sctdata;
287 
288  sctdata = g_malloc(sizeof(sc_tdata));
289  sctdata->show_only = show_only;
290 
291  g_thread_new("stats_collector", remmina_stats_collector, (gpointer)sctdata);
292 
293 }
294 
296 {
298  return TRUE;
299  else
300  return FALSE;
301 }
302 
303 static gboolean remmina_stats_sender_periodic_check(gpointer user_data)
304 {
305  TRACE_CALL(__func__);
306  GTimeVal t;
307  glong next;
308 
310  return G_SOURCE_REMOVE;
311 
312  /* Calculate "next" upload time based on last sent time */
313  next = remmina_pref.periodic_usage_stats_last_sent + PERIODIC_UPLOAD_INTERVAL_SEC;
314  g_get_current_time(&t);
315  /* If current time is after "next" or clock is going back (but > 1/1/2018), then do send stats */
316  if (t.tv_sec > next || (t.tv_sec < remmina_pref.periodic_usage_stats_last_sent && t.tv_sec > 1514764800)) {
318  }
319 
320  return G_SOURCE_CONTINUE;
321 }
322 
324 {
325  TRACE_CALL(__func__);
327  NULL,
328  PERIODIC_CHECK_1ST_MS,
329  PERIODIC_CHECK_INTERVAL_MS);
330 }
static gboolean remmina_stats_collector_done(gpointer data)
gboolean show_only
static gpointer remmina_stats_collector(gpointer data)
void * remmina_scheduler_setup(GSourceFunc cb, gpointer cb_data, guint first_interval, guint interval)
void remmina_debug(const gchar *fmt,...)
Print a string in the Remmina Debug Windows and in the terminal.
Definition: remmina_log.c:194
static char * remmina_RSA_PubKey_v1
static SoupSession * session
Definition: rmnews.c:77
static void soup_callback(SoupSession *session, SoupMessage *msg, gpointer user_data)
gboolean periodic_usage_stats_permitted
Definition: remmina_pref.h:200
RemminaPref remmina_pref
Definition: rcw.c:73
void remmina_stats_sender_schedule()
static gchar * rsa_encrypt_string(RSA *pubKey, const char *instr)
JsonNode * remmina_stats_get_all()
Get all statistics in json format to send periodically to the PHP server.
glong periodic_usage_stats_last_sent
Definition: remmina_pref.h:201
gboolean remmina_pref_save(void)
Definition: remmina_pref.c:707
JsonNode * statsroot
static gboolean remmina_stats_sender_periodic_check(gpointer user_data)
gboolean remmina_stat_sender_can_send()
void remmina_stats_sender_send(gboolean show_only)