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 /**
36 * General utility functions, non-GTK related.
37 */
38
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <sys/utsname.h>
42 #include <locale.h>
43
44 #include <glib.h>
45 #include <glib/gi18n.h>
46 #include <glib/gstdio.h>
47 #include <gio/gio.h>
48 #include "remmina_sodium.h"
49 #include "remmina/remmina_trace_calls.h"
50
51 /** Returns @c TRUE if @a ptr is @c NULL or @c *ptr is @c FALSE. */
52 #define EMPTY(ptr) \
53 (!(ptr) || !*(ptr))
54
55 struct utsname u;
56
57 /* Copyright (C) 1998 VMware, Inc. All rights reserved.
58 * Some of the code in this file is taken from the VMware open client.
59 */
60 typedef struct lsb_distro_info {
61 gchar * name;
62 gchar * scanstring;
63 } LSBDistroInfo;
64
65 /*
66 * static LSBDistroInfo lsbFields[] = {
67 * { "DISTRIB_ID=", "DISTRIB_ID=%s" },
68 * { "DISTRIB_RELEASE=", "DISTRIB_RELEASE=%s" },
69 * { "DISTRIB_CODENAME=", "DISTRIB_CODENAME=%s" },
70 * { "DISTRIB_DESCRIPTION=", "DISTRIB_DESCRIPTION=%s" },
71 * { NULL, NULL },
72 * };
73 */
74
75 typedef struct distro_info {
76 gchar * name;
77 gchar * filename;
78 } DistroInfo;
79
80 static DistroInfo distroArray[] = {
81 { "RedHat", "/etc/redhat-release" },
82 { "RedHat", "/etc/redhat_version" },
83 { "Sun", "/etc/sun-release" },
84 { "SuSE", "/etc/SuSE-release" },
85 { "SuSE", "/etc/novell-release" },
86 { "SuSE", "/etc/sles-release" },
87 { "SuSE", "/etc/os-release" },
88 { "Debian", "/etc/debian_version" },
89 { "Debian", "/etc/debian_release" },
90 { "Ubuntu", "/etc/lsb-release" },
91 { "Mandrake", "/etc/mandrake-release" },
92 { "Mandriva", "/etc/mandriva-release" },
93 { "Mandrake", "/etc/mandrakelinux-release" },
94 { "TurboLinux", "/etc/turbolinux-release" },
95 { "Fedora Core", "/etc/fedora-release" },
96 { "Gentoo", "/etc/gentoo-release" },
97 { "Novell", "/etc/nld-release" },
98 { "Annvix", "/etc/annvix-release" },
99 { "Arch", "/etc/arch-release" },
100 { "Arklinux", "/etc/arklinux-release" },
101 { "Aurox", "/etc/aurox-release" },
102 { "BlackCat", "/etc/blackcat-release" },
103 { "Cobalt", "/etc/cobalt-release" },
104 { "Conectiva", "/etc/conectiva-release" },
105 { "Immunix", "/etc/immunix-release" },
106 { "Knoppix", "/etc/knoppix_version" },
107 { "Linux-From-Scratch", "/etc/lfs-release" },
108 { "Linux-PPC", "/etc/linuxppc-release" },
109 { "MkLinux", "/etc/mklinux-release" },
110 { "PLD", "/etc/pld-release" },
111 { "Slackware", "/etc/slackware-version" },
112 { "Slackware", "/etc/slackware-release" },
113 { "SMEServer", "/etc/e-smith-release" },
114 { "Solaris", "/etc/release" },
115 { "Solus", "/etc/solus-release" },
116 { "Tiny Sofa", "/etc/tinysofa-release" },
117 { "UltraPenguin", "/etc/ultrapenguin-release" },
118 { "UnitedLinux", "/etc/UnitedLinux-release" },
119 { "VALinux", "/etc/va-release" },
120 { "Yellow Dog", "/etc/yellowdog-release" },
121 { NULL, NULL },
122 };
123
remmina_utils_strpos(const gchar * haystack,const gchar * needle)124 gint remmina_utils_strpos(const gchar *haystack, const gchar *needle)
125 {
126 TRACE_CALL(__func__);
127 const gchar *sub;
128
129 if (!*needle)
130 return -1;
131
132 sub = strstr(haystack, needle);
133 if (!sub)
134 return -1;
135
136 return sub - haystack;
137 }
138
139 /* end can be -1 for haystack->len.
140 * returns: position of found text or -1.
141 * (C) Taken from geany */
remmina_utils_string_find(GString * haystack,gint start,gint end,const gchar * needle)142 gint remmina_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
143 {
144 TRACE_CALL(__func__);
145 gint pos;
146
147 g_return_val_if_fail(haystack != NULL, -1);
148 if (haystack->len == 0)
149 return -1;
150
151 g_return_val_if_fail(start >= 0, -1);
152 if (start >= (gint)haystack->len)
153 return -1;
154
155 g_return_val_if_fail(!EMPTY(needle), -1);
156
157 if (end < 0)
158 end = haystack->len;
159
160 pos = remmina_utils_strpos(haystack->str + start, needle);
161 if (pos == -1)
162 return -1;
163
164 pos += start;
165 if (pos >= end)
166 return -1;
167 return pos;
168 }
169
170 /* Replaces @len characters from offset @a pos.
171 * len can be -1 to replace the remainder of @a str.
172 * returns: pos + strlen(replace).
173 * (C) Taken from geany */
remmina_utils_string_replace(GString * str,gint pos,gint len,const gchar * replace)174 gint remmina_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
175 {
176 TRACE_CALL(__func__);
177 g_string_erase(str, pos, len);
178 if (replace) {
179 g_string_insert(str, pos, replace);
180 pos += strlen(replace);
181 }
182 return pos;
183 }
184
185 /**
186 * Replaces all occurrences of @a needle in @a haystack with @a replace.
187 *
188 * @param haystack The input string to operate on. This string is modified in place.
189 * @param needle The string which should be replaced.
190 * @param replace The replacement for @a needle.
191 *
192 * @return Number of replacements made.
193 **/
remmina_utils_string_replace_all(GString * haystack,const gchar * needle,const gchar * replace)194 guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
195 {
196 TRACE_CALL(__func__);
197 guint count = 0;
198 gint pos = 0;
199 gsize needle_length = strlen(needle);
200
201 while (1) {
202 pos = remmina_utils_string_find(haystack, pos, -1, needle);
203
204 if (pos == -1)
205 break;
206
207 pos = remmina_utils_string_replace(haystack, pos, needle_length, replace);
208 count++;
209 }
210 return count;
211 }
212
213 /**
214 * Strip \n, \t and \" from a given string.
215 * This function is particularly useful with g_spawn_command_line_sync that does
216 * not strip control characters from the output.
217 * @warning the result should be freed.
218 * @param a string.
219 * @return a newly allocated copy of string cleaned by \t, \n and \"
220 */
remmina_utils_string_strip(const gchar * s)221 gchar *remmina_utils_string_strip(const gchar *s)
222 {
223 gchar *p = g_malloc(strlen(s) + 1);
224
225 if (p) {
226 gchar *p2 = p;
227 while (*s != '\0') {
228 if (*s != '\t' && *s != '\n' && *s != '\"')
229 *p2++ = *s++;
230 else
231 ++s;
232 }
233 *p2 = '\0';
234 }
235 return p;
236 }
237
238 /** OS related functions */
239
240 /**
241 * remmina_utils_read_distrofile.
242 *
243 * Look for a distro version file /etc/xxx-release.
244 * Once found, read the file in and figure out which distribution.
245 *
246 * @param filename The file path of a Linux distribution release file.
247 * @param distroSize The size of the distribution name.
248 * @param distro The full distro name.
249 * @return Returns a string containing distro information verbatim from /etc/xxx-release (distro). Use g_free to free the string.
250 *
251 */
remmina_utils_read_distrofile(gchar * filename)252 static gchar *remmina_utils_read_distrofile(gchar *filename)
253 {
254 TRACE_CALL(__func__);
255 gsize file_sz;
256 struct stat st;
257 gchar *distro_desc = NULL;
258 GError *err = NULL;
259
260 if (g_stat(filename, &st) == -1) {
261 g_debug("%s: could not stat the file %s\n", __func__, filename);
262 return NULL;
263 }
264
265 g_debug("%s: File %s is %lu bytes long\n", __func__, filename, st.st_size);
266 if (st.st_size > 131072)
267 return NULL;
268
269 if (!g_file_get_contents(filename, &distro_desc, &file_sz, &err)) {
270 g_debug("%s: could not get the file content%s: %s\n", __func__, filename, err->message);
271 g_error_free(err);
272 return NULL;
273 }
274
275 if (file_sz == 0) {
276 g_debug("%s: Cannot work with empty file.\n", __FUNCTION__);
277 return NULL;
278 }
279
280 g_debug("%s: Distro description %s\n", __func__, distro_desc);
281 return distro_desc;
282 }
283
284 /**
285 * Return the current language defined in the LC_ALL.
286 * @return a language string or en_US.
287 */
remmina_utils_get_lang()288 gchar *remmina_utils_get_lang()
289 {
290 gchar *lang = setlocale(LC_ALL, NULL);
291 gchar *ptr;
292
293 if (!lang || lang[0] == '\0') {
294 lang = "en_US\0";
295 } else {
296 ptr = strchr(lang, '.');
297 if (ptr != NULL)
298 *ptr = '\0';
299 }
300
301 return lang;
302 }
303 /**
304 * Return the OS name as in "uname -s".
305 * @return The OS name or NULL.
306 */
remmina_utils_get_kernel_name()307 const gchar *remmina_utils_get_kernel_name()
308 {
309 TRACE_CALL(__func__);
310 return u.sysname;
311 }
312
remmina_utils_get_kernel_release()313 const gchar *remmina_utils_get_kernel_release()
314 /**
315 * Return the OS version as in "uname -r".
316 * @return The OS release or NULL.
317 */
318 {
319 TRACE_CALL(__func__);
320 return u.release;
321 }
322
323 /**
324 * Return the machine hardware name as in "uname -m".
325 * @return The machine hardware name or NULL.
326 */
remmina_utils_get_kernel_arch()327 const gchar *remmina_utils_get_kernel_arch()
328 {
329 TRACE_CALL(__func__);
330 return u.machine;
331 }
332
333 /**
334 * Print the Distributor as specified by the lsb_release command.
335 * @return the distributor ID string or NULL. Caller must free it with g_free().
336 */
remmina_utils_get_lsb_id()337 gchar *remmina_utils_get_lsb_id()
338 {
339 TRACE_CALL(__func__);
340 gchar *lsb_id = NULL;
341 if (g_spawn_command_line_sync("/usr/bin/lsb_release -si", &lsb_id, NULL, NULL, NULL))
342 return lsb_id;
343 return NULL;
344 }
345
346 /**
347 * Print the Distribution description as specified by the lsb_release command.
348 * @return the Distribution description string or NULL. Caller must free it with g_free().
349 */
remmina_utils_get_lsb_description()350 gchar *remmina_utils_get_lsb_description()
351 {
352 TRACE_CALL(__func__);
353 gchar *lsb_description = NULL;
354 GError *err = NULL;
355
356 if (g_spawn_command_line_sync("/usr/bin/lsb_release -sd", &lsb_description, NULL, NULL, &err)) {
357 return lsb_description;
358 } else {
359 g_debug("%s: could not execute lsb_release %s\n", __func__, err->message);
360 g_error_free(err);
361 }
362 g_debug("%s: lsb_release %s\n", __func__, lsb_description);
363 return NULL;
364 }
365
366 /**
367 * Print the Distribution release name as specified by the lsb_release command.
368 * @return the Distribution release name string or NULL. Caller must free it with g_free().
369 */
remmina_utils_get_lsb_release()370 gchar *remmina_utils_get_lsb_release()
371 {
372 TRACE_CALL(__func__);
373 gchar *lsb_release = NULL;
374 if (g_spawn_command_line_sync("/usr/bin/lsb_release -sr", &lsb_release, NULL, NULL, NULL))
375 return lsb_release;
376 return NULL;
377 }
378
379 /**
380 * Print the Distribution codename as specified by the lsb_release command.
381 * @return the codename string or NULL. Caller must free it with g_free().
382 */
remmina_utils_get_lsb_codename()383 gchar *remmina_utils_get_lsb_codename()
384 {
385 TRACE_CALL(__func__);
386 gchar *lsb_codename = NULL;
387 if (g_spawn_command_line_sync("/usr/bin/lsb_release -sc", &lsb_codename, NULL, NULL, NULL))
388 return lsb_codename;
389 return NULL;
390 }
391
392 /**
393 * Print the distribution description if found.
394 * Test each known distribution specific information file and print it’s content.
395 * @return a string or NULL. Caller must free it with g_free().
396 */
remmina_utils_get_etc_release()397 GHashTable *remmina_utils_get_etc_release()
398 {
399 TRACE_CALL(__func__);
400 gchar *etc_release = NULL;
401 gint i;
402 GHashTable *r;
403
404 r = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
405
406 for (i = 0; distroArray[i].filename != NULL; i++) {
407 g_debug("%s: File %s\n", __func__, distroArray[i].filename);
408 etc_release = remmina_utils_read_distrofile(distroArray[i].filename);
409 if (etc_release) {
410 if (etc_release[0] != '\0') {
411 g_debug("%s: Distro description %s\n", __func__, etc_release);
412 g_hash_table_insert(r, distroArray[i].filename, etc_release);
413 } else {
414 g_free(etc_release);
415 }
416 }
417 }
418 return r;
419 }
420
421 /**
422 * A sample function to show how use the other fOS related functions.
423 * @return a semicolon separated OS data like in "uname -srm".
424 */
remmina_utils_get_os_info()425 const gchar *remmina_utils_get_os_info()
426 {
427 TRACE_CALL(__func__);
428 gchar *kernel_string;
429
430 if (uname(&u) == -1)
431 g_print("uname:");
432
433 kernel_string = g_strdup_printf("%s;%s;%s\n",
434 remmina_utils_get_kernel_name(),
435 remmina_utils_get_kernel_release(),
436 remmina_utils_get_kernel_arch());
437 if (!kernel_string || kernel_string[0] == '\0') {
438 if (kernel_string)
439 g_free(kernel_string);
440 kernel_string = g_strdup_printf("%s;%s;%s\n",
441 "UNKNOWN",
442 "UNKNOWN",
443 "UNKNOWN");
444 }
445 return kernel_string;
446 }
447
448 /**
449 * Create a hexadecimal string version of the SHA-1 digest of the
450 * contents of the named file.
451 *
452 * @return a newly allocated string which the caller
453 * should free() when finished.
454 *
455 * If any error occurs while reading the file, (permission denied,
456 * file not found, etc.), this function returns NULL.
457 *
458 * Taken from https://github.com/ttuegel/notmuch do PR in case of substantial modifications.
459 *
460 */
remmina_sha1_file(const gchar * filename)461 gchar *remmina_sha1_file(const gchar *filename)
462 {
463 FILE *file;
464
465 #define BLOCK_SIZE 4096
466 unsigned char block[BLOCK_SIZE];
467 size_t bytes_read;
468 GChecksum *sha1;
469 char *digest = NULL;
470
471 file = fopen(filename, "r");
472 if (file == NULL)
473 return NULL;
474
475 sha1 = g_checksum_new(G_CHECKSUM_SHA1);
476 if (sha1 == NULL)
477 goto DONE;
478
479 while (1) {
480 bytes_read = fread(block, 1, 4096, file);
481 if (bytes_read == 0) {
482 if (feof(file))
483 break;
484 else if (ferror(file))
485 goto DONE;
486 } else {
487 g_checksum_update(sha1, block, bytes_read);
488 }
489 }
490
491 digest = g_strdup(g_checksum_get_string(sha1));
492
493 DONE:
494 if (sha1)
495 g_checksum_free(sha1);
496 if (file)
497 fclose(file);
498
499 return digest;
500 }
501
502 /**
503 * Generate a random sting of chars to be used as part of UID for news or stats
504 * @return a string or NULL. Caller must free it with g_free().
505 */
remmina_gen_random_uuid()506 gchar *remmina_gen_random_uuid()
507 {
508 TRACE_CALL(__func__);
509 gchar *result;
510 int i;
511 static char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
512
513 result = g_malloc0(15);
514
515 for (i = 0; i < 7; i++)
516 result[i] = alpha[randombytes_uniform(sizeof(alpha))];
517
518 for (i = 0; i < 7; i++)
519 result[i + 7] = alpha[randombytes_uniform(sizeof(alpha))];
520
521 return result;
522 }
523