Remmina - The GTK+ Remote Desktop Client  v1.4.22
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-2021 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  GDateTime *gdt;
87  gint64 unixts;
88 
89  g_free(s);
90 
91  if (msg->status_code != 200) {
92  REMMINA_DEBUG("HTTP status error sending stats: %d\n", msg->status_code);
93  return;
94  }
95 
96  gdt = g_date_time_new_now_utc();
97  unixts = g_date_time_to_unix(gdt);
98  g_date_time_to_unix(gdt);
99 
100  passed = FALSE;
101  sb = soup_message_body_flatten(msg->response_body);
102  REMMINA_DEBUG("STATS script response: %.40s\n", sb->data);
103  if (strncmp(sb->data, "200 ", 4) != 0) {
104  REMMINA_DEBUG("STATS http upload error from server side script: %s\n", sb->data);
105  } else {
106  passed = TRUE;
107  }
108  soup_buffer_free(sb);
109 
110  if (passed) {
113  }
114 
115 }
116 
117 static gchar *rsa_encrypt_string(RSA *pubKey, const char *instr)
118 {
119  TRACE_CALL(__func__);
120  /* Calls RSA_public_encrypt multiple times to encrypt instr.
121  * At the end, base64 encode the resulting buffer
122  * Return a buffer ptr. Use g_free() to deallocate it */
123 
124  int rsaLen = RSA_size(pubKey);
125  int inLen = strlen(instr);
126  int remaining, r;
127  int blksz, maxblksz;
128  int ebufSize;
129  unsigned char *ebuf, *outptr;
130  gchar *enc;
131 
132  maxblksz = rsaLen - 12;
133  ebufSize = (((inLen - 1) / maxblksz) + 1) * rsaLen;
134  ebuf = g_malloc(ebufSize);
135  outptr = ebuf;
136  remaining = strlen(instr);
137 
138  while(remaining > 0) {
139  blksz = remaining > maxblksz ? maxblksz : remaining;
140  r = RSA_public_encrypt(blksz,
141  (const unsigned char *)instr,
142  outptr,
143  pubKey, RSA_PKCS1_PADDING); /* Our poor JS libraries only supports RSA_PKCS1_PADDING */
144  if (r == -1 ) {
145  unsigned long e;
146  ERR_load_crypto_strings();
147  e = ERR_get_error();
148  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));
149  g_free(ebuf);
150  ERR_free_strings();
151  return NULL;
152  }
153  instr += blksz;
154  remaining -= blksz;
155  outptr += r;
156  }
157 
158  enc = g_base64_encode(ebuf, ebufSize);
159  g_free(ebuf);
160 
161  return enc;
162 
163 
164 }
165 
166 static gboolean remmina_stats_collector_done(gpointer data)
167 {
168  TRACE_CALL(__func__);
169  JsonNode *n;
170  JsonGenerator *g;
171  gchar *unenc_s, *enc_s;
172  SoupSession *ss;
173  SoupMessage *msg;
174  JsonBuilder *b;
175  JsonObject *o;
176  BIO *pkbio;
177  RSA *pubkey;
178  gchar *uid;
179  sc_tdata *sctdata;
180 
181  sctdata = (sc_tdata *)data;
182  if (sctdata == NULL)
183  return G_SOURCE_REMOVE;
184 
185  n = sctdata->statsroot;
186  if (n == NULL) {
187  g_free(data);
188  return G_SOURCE_REMOVE;
189  }
190 
191  if ((o = json_node_get_object(n)) == NULL) {
192  g_free(data);
193  return G_SOURCE_REMOVE;
194  }
195 
196  uid = g_strdup(json_object_get_string_member(o, "UID"));
197 
198  g = json_generator_new();
199  json_generator_set_root(g, n);
200  json_node_unref(n);
201  unenc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
202  REMMINA_DEBUG("STATS upload: JSON data%s\n", unenc_s);
203  g_object_unref(g);
204 
205  /* Now encrypt "s" with remminastats public key */
206 
207  pkbio = BIO_new_mem_buf(remmina_RSA_PubKey_v1, -1);
208  pubkey = PEM_read_bio_RSA_PUBKEY(pkbio, NULL, NULL, NULL);
209  if (pubkey == NULL) {
210  ERR_load_crypto_strings();
211  unsigned long e;
212  e = ERR_get_error();
213  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));
214  g_print("%s\n", ERR_error_string( e, NULL ));
215  BIO_free(pkbio);
216  g_free(unenc_s);
217  ERR_free_strings();
218  g_free(data);
219  g_free(uid);
220  return G_SOURCE_REMOVE;
221  }
222 
223  enc_s = rsa_encrypt_string(pubkey, unenc_s);
224 
225  g_free(unenc_s);
226  BIO_free(pkbio);
227 
228 
229  /* Create new json encrypted object */
230 
231  b = json_builder_new();
232  json_builder_begin_object(b);
233  json_builder_set_member_name(b, "keyversion");
234  json_builder_add_int_value(b, 1);
235  json_builder_set_member_name(b, "encdata");
236  json_builder_add_string_value(b, enc_s);
237  json_builder_set_member_name(b, "UID");
238  json_builder_add_string_value(b, uid);
239  json_builder_end_object(b);
240  n = json_builder_get_root(b);
241  g_object_unref(b);
242 
243  g_free(uid);
244  g_free(enc_s);
245 
246  if (!sctdata->show_only) {
247 
248  g = json_generator_new();
249  json_generator_set_root(g, n);
250  enc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
251  g_object_unref(g);
252 
253  ss = soup_session_new();
254  msg = soup_message_new("POST", PERIODIC_UPLOAD_URL);
255  soup_message_set_request(msg, "application/json",
256  SOUP_MEMORY_COPY, enc_s, strlen(enc_s));
257  soup_session_queue_message(ss, msg, soup_callback, enc_s);
258 
259  REMMINA_DEBUG("STATS upload: Starting upload to url %s\n", PERIODIC_UPLOAD_URL);
260  }
261 
262  json_node_unref(n);
263  g_free(data);
264 
265  return G_SOURCE_REMOVE;
266 }
267 
268 
269 static gpointer remmina_stats_collector(gpointer data)
270 {
271  TRACE_CALL(__func__);
272  JsonNode *n;
273  sc_tdata *sctdata;
274 
275  sctdata = (sc_tdata *)data;
276  n = remmina_stats_get_all();
277 
278  /* stats collecting is done. Notify main thread calling
279  * remmina_stats_collector_done() */
280  sctdata->statsroot = n;
281  g_idle_add(remmina_stats_collector_done, sctdata);
282 
283  return NULL;
284 }
285 
286 void remmina_stats_sender_send(gboolean show_only)
287 {
288  TRACE_CALL(__func__);
289 
290  sc_tdata *sctdata;
291 
292  sctdata = g_malloc(sizeof(sc_tdata));
293  sctdata->show_only = show_only;
294 
295  g_thread_new("stats_collector", remmina_stats_collector, (gpointer)sctdata);
296 
297 }
298 
300 {
302  return TRUE;
303  else
304  return FALSE;
305 }
306 
307 static gboolean remmina_stats_sender_periodic_check(gpointer user_data)
308 {
309  TRACE_CALL(__func__);
310  GDateTime *gdt;
311  gint64 unixts;
312  glong next;
313 
314  gdt = g_date_time_new_now_utc();
315  unixts = g_date_time_to_unix(gdt);
316  g_date_time_to_unix(gdt);
317 
319  return G_SOURCE_REMOVE;
320 
321  /* Calculate "next" upload time based on last sent time */
322  next = remmina_pref.periodic_usage_stats_last_sent + PERIODIC_UPLOAD_INTERVAL_SEC;
323  /* If current time is after "next" or clock is going back (but > 1/1/2018), then do send stats */
324  if (unixts > next || (unixts < remmina_pref.periodic_usage_stats_last_sent && unixts > 1514764800)) {
326  }
327 
328  return G_SOURCE_CONTINUE;
329 }
330 
332 {
333  TRACE_CALL(__func__);
335  NULL,
336  PERIODIC_CHECK_1ST_MS,
337  PERIODIC_CHECK_INTERVAL_MS);
338 }
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)
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:214
RemminaPref remmina_pref
Definition: rcw.c:75
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:215
gboolean remmina_pref_save(void)
Definition: remmina_pref.c:760
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)