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