/* * 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. * */ /** * General utility functions, non-GTK related. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "remmina_sodium.h" #include "remmina/remmina_trace_calls.h" #include "remmina_utils.h" #include "remmina_log.h" #include "remmina_plugin_manager.h" /** Returns @c TRUE if @a ptr is @c NULL or @c *ptr is @c FALSE. */ #define EMPTY(ptr) \ (!(ptr) || !*(ptr)) /* Copyright (C) 1998 VMware, Inc. All rights reserved. * Some of the code in this file is taken from the VMware open client. */ typedef struct lsb_distro_info { gchar * name; gchar * scanstring; } LSBDistroInfo; /* * static LSBDistroInfo lsbFields[] = { * { "DISTRIB_ID=", "DISTRIB_ID=%s" }, * { "DISTRIB_RELEASE=", "DISTRIB_RELEASE=%s" }, * { "DISTRIB_CODENAME=", "DISTRIB_CODENAME=%s" }, * { "DISTRIB_DESCRIPTION=", "DISTRIB_DESCRIPTION=%s" }, * { NULL, NULL }, * }; */ typedef struct distro_info { gchar * name; gchar * filename; } DistroInfo; static DistroInfo distroArray[] = { { "RedHat", "/etc/redhat-release" }, { "RedHat", "/etc/redhat_version" }, { "Sun", "/etc/sun-release" }, { "SuSE", "/etc/SuSE-release" }, { "SuSE", "/etc/novell-release" }, { "SuSE", "/etc/sles-release" }, { "SuSE", "/etc/os-release" }, { "Debian", "/etc/debian_version" }, { "Debian", "/etc/debian_release" }, { "Ubuntu", "/etc/lsb-release" }, { "Mandrake", "/etc/mandrake-release" }, { "Mandriva", "/etc/mandriva-release" }, { "Mandrake", "/etc/mandrakelinux-release" }, { "TurboLinux", "/etc/turbolinux-release" }, { "Fedora Core", "/etc/fedora-release" }, { "Gentoo", "/etc/gentoo-release" }, { "Novell", "/etc/nld-release" }, { "Annvix", "/etc/annvix-release" }, { "Arch", "/etc/arch-release" }, { "Arklinux", "/etc/arklinux-release" }, { "Aurox", "/etc/aurox-release" }, { "BlackCat", "/etc/blackcat-release" }, { "Cobalt", "/etc/cobalt-release" }, { "Conectiva", "/etc/conectiva-release" }, { "Immunix", "/etc/immunix-release" }, { "Knoppix", "/etc/knoppix_version" }, { "Linux-From-Scratch", "/etc/lfs-release" }, { "Linux-PPC", "/etc/linuxppc-release" }, { "MkLinux", "/etc/mklinux-release" }, { "PLD", "/etc/pld-release" }, { "Slackware", "/etc/slackware-version" }, { "Slackware", "/etc/slackware-release" }, { "SMEServer", "/etc/e-smith-release" }, { "Solaris", "/etc/release" }, { "Solus", "/etc/solus-release" }, { "Tiny Sofa", "/etc/tinysofa-release" }, { "UltraPenguin", "/etc/ultrapenguin-release" }, { "UnitedLinux", "/etc/UnitedLinux-release" }, { "VALinux", "/etc/va-release" }, { "Yellow Dog", "/etc/yellowdog-release" }, { NULL, NULL }, }; const char *remmina_RSA_PubKey_v1 = "-----BEGIN PUBLIC KEY-----\n" "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyTig5CnpPOIZQn5yVfsg\n" "Y+fNzVS3u0zgyZ7QPstVMgCR234KobRDy/6NOa6tHAzohn166dYZOrkOqFtAUUzQ\n" "wVT0P9KkA0RTIChNBCA7G+LE3BYgomoSm2ofBRsfax8RdwYzsqArBHwluv3F6zOT\n" "aLwUbtUbDYRwzeIOlfcBLxNlPxHKR8Jq9zREVaEHm8Vj8/e6vNZ66v5I8tg+NaUV\n" "omR1h0SPfU0R77x1q/LX41jHISq/cCRLoi4ft+zEJ03HhtmPeV84DfWwtQtpv9P4\n" "q2bC5pEH73VHs/igVC44JA9eNcJ/qGxd1uI7K3DGrqtVfa+csEzGtWD3THEo/ZXW\n" "bBlGIbOvh58DxKpheR4C3xzWJfeHRUPueGaanwpALydaV5uE4yFwPmQ9KvBhEQAg\n" "ck9d10FPKQfHXOAfXkTWToeVLoIk/oDIo9XUbEnFHELtw3gIGNHpMUmUOEk7th80\n" "Cldf86fyEw0SPnP+y+SR6gw1zOvs1gbBfMxHsNKIfmpps162JV0bpP0vz7eZMyme\n" "EuEEJ6TiyPHLaHaAluljc4UlpA+Huh/S8pZeObUhFpk3bvKVol/BHp3p388PRzZ1\n" "eYLNXO8muQqL2SX4k9ndFggsDnr2QYp/dkLaoclNJUiBWPDSHDCuRMX1rCm+wv1p\n" "wGWm2KJS9Iz7J5Bc19pcm90CAwEAAQ==\n" "-----END PUBLIC KEY-----\n"; const char *remmina_RSA_PubKey_v2 = "-----BEGIN PUBLIC KEY-----\n" "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxn7UtcGVxDbTvbprKNnw\n" "CQws91Gg7as+A6PcqJ1GkRP46OKEiwJcVigbKZH53LQXLZUY+kOTYtD21+IicrAP\n" "QL1er52oQf47ECEsNQ94kNirs/iqoGo+igNd+4scpFhm8WnrPgVvjbulqwGFY40d\n" "/zqHaKgYhLvt8OUXF+YSIAC8cQ5qkXy8ncnrpBi22Slly5MyPfXciZj4oKvtXA54\n" "8+TP9E+hwagH6xF7aH6fyEgTmzuI2y4hXzkWRXNg5umifpkdHfppWETA+FHIZ7Dh\n" "/jy2NnR3wvoDMSqxNaea/LrGJS2cCQpO5d0EzAT+umA4ou2iDi6DfUgkAU/Vh7Jt\n" "fedB6x2iewsMU12ef6UkRFrcf7v1tgfAjbWDUXCZTR5zNBm+nrWI6J8jsQ2h6ykP\n" "qFdcUjMilLisDB8XPccUoEa0xCAMS2CgPiaC/CPeZoxpBXXxUlcwHUKLpl7wKxn7\n" "/MxAu+ynbfL44xEJ74ka1z06Zi7pa4v16Pv0CAgoIRWI0mvZV7iANiBIvNQ5jE8I\n" "k273owYRDfnZEHbFMF3TBzFdG6dALpJYqPA649p6VKNwoEksubf9ygWHWmaheUA1\n" "Vq3SYIEx+Kymr650VdWLVXrLF+Gl+QXcGtfd5rTnVwW+erKWJU8bFDkmKLw8xADC\n" "LlH/YYsHZOx0hXoxQJf+VHcCAwEAAQ==\n" "-----END PUBLIC KEY-----\n"; gint remmina_utils_strpos(const gchar *haystack, const gchar *needle) { TRACE_CALL(__func__); const gchar *sub; if (!*needle) return -1; sub = strstr(haystack, needle); if (!sub) return -1; return sub - haystack; } /* end can be -1 for haystack->len. * returns: position of found text or -1. * (C) Taken from geany */ gint remmina_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle) { TRACE_CALL(__func__); gint pos; g_return_val_if_fail(haystack != NULL, -1); if (haystack->len == 0) return -1; g_return_val_if_fail(start >= 0, -1); if (start >= (gint)haystack->len) return -1; g_return_val_if_fail(!EMPTY(needle), -1); if (end < 0) end = haystack->len; pos = remmina_utils_strpos(haystack->str + start, needle); if (pos == -1) return -1; pos += start; if (pos >= end) return -1; return pos; } /* Replaces @len characters from offset @a pos. * len can be -1 to replace the remainder of @a str. * returns: pos + strlen(replace). * (C) Taken from geany */ gint remmina_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace) { TRACE_CALL(__func__); g_string_erase(str, pos, len); if (replace) { g_string_insert(str, pos, replace); pos += strlen(replace); } return pos; } /** * Replaces all occurrences of @a needle in @a haystack with @a replace. * * @param haystack The input string to operate on. This string is modified in place. * @param needle The string which should be replaced. * @param replace The replacement for @a needle. * * @return Number of replacements made. **/ guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace) { TRACE_CALL(__func__); guint count = 0; gint pos = 0; gsize needle_length = strlen(needle); while (1) { pos = remmina_utils_string_find(haystack, pos, -1, needle); if (pos == -1) break; pos = remmina_utils_string_replace(haystack, pos, needle_length, replace); count++; } return count; } /** * Strip \n, \t and \" from a given string. * This function is particularly useful with g_spawn_command_line_sync that does * not strip control characters from the output. * @warning the result should be freed. * @param a string. * @return a newly allocated copy of string cleaned by \t, \n and \" */ gchar *remmina_utils_string_strip(const gchar *s) { gchar *p = g_malloc(strlen(s) + 1); if (p == NULL) { return NULL; } if (p) { gchar *p2 = p; while (*s != '\0') { if (*s != '\t' && *s != '\n' && *s != '\"') *p2++ = *s++; else ++s; } *p2 = '\0'; } return p; } /** OS related functions */ /** * remmina_utils_read_distrofile. * * Look for a distro version file /etc/xxx-release. * Once found, read the file in and figure out which distribution. * * @param filename The file path of a Linux distribution release file. * @param distroSize The size of the distribution name. * @param distro The full distro name. * @return Returns a string containing distro information verbatim from /etc/xxx-release (distro). Use g_free to free the string. * */ static gchar *remmina_utils_read_distrofile(gchar *filename) { TRACE_CALL(__func__); gsize file_sz; struct stat st; gchar *distro_desc = NULL; GError *err = NULL; if (g_stat(filename, &st) == -1) { return NULL; } if (st.st_size > 131072) return NULL; if (!g_file_get_contents(filename, &distro_desc, &file_sz, &err)) { g_error_free(err); return NULL; } if (file_sz == 0) { return NULL; } return distro_desc; } /** * Return the current language defined in the LC_ALL. * @return a language string or en_US. */ gchar *remmina_utils_get_lang() { TRACE_CALL(__func__); gchar *lang = setlocale(LC_ALL, NULL); gchar *ptr; if (!lang || lang[0] == '\0') { lang = "en_US\0"; } else { ptr = strchr(lang, '.'); if (ptr != NULL) *ptr = '\0'; } return lang; } /** * Return the OS name as in "uname -s". * @return The OS name or NULL. */ gchar *remmina_utils_get_kernel_name() { TRACE_CALL(__func__); struct utsname u; if (uname(&u) == -1) { return NULL; } return g_strdup(u.sysname); } /** * Return the OS version as in "uname -r". * @return The OS release or NULL. */ gchar *remmina_utils_get_kernel_release() { TRACE_CALL(__func__); struct utsname u; if (uname(&u) == -1) { return NULL; } return g_strdup(u.release); } /** * Return the machine hardware name as in "uname -m". * @return The machine hardware name or NULL. */ gchar *remmina_utils_get_kernel_arch() { TRACE_CALL(__func__); struct utsname u; if (uname(&u) == -1) { return NULL; } return g_strdup(u.machine); } /** * Print the Distributor as specified by the lsb_release command. * @return the distributor ID string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_lsb_id() { TRACE_CALL(__func__); gchar *lsb_id = NULL; if (g_spawn_command_line_sync("/usr/bin/lsb_release -si", &lsb_id, NULL, NULL, NULL)) return lsb_id; return NULL; } /** * Print the Distribution description as specified by the lsb_release command. * @return the Distribution description string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_lsb_description() { TRACE_CALL(__func__); gchar *lsb_description = NULL; if (g_spawn_command_line_sync("/usr/bin/lsb_release -sd", &lsb_description, NULL, NULL, NULL)) return lsb_description; return NULL; } /** * Print the Distribution release name as specified by the lsb_release command. * @return the Distribution release name string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_lsb_release() { TRACE_CALL(__func__); gchar *lsb_release = NULL; if (g_spawn_command_line_sync("/usr/bin/lsb_release -sr", &lsb_release, NULL, NULL, NULL)) return lsb_release; return NULL; } /** * Print the Distribution codename as specified by the lsb_release command. * @return the codename string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_lsb_codename() { TRACE_CALL(__func__); gchar *lsb_codename = NULL; if (g_spawn_command_line_sync("/usr/bin/lsb_release -sc", &lsb_codename, NULL, NULL, NULL)) return lsb_codename; return NULL; } /** * Print the distribution description if found. * Test each known distribution specific information file and print it’s content. * @return a string or NULL. Caller must free it with g_free(). */ GHashTable *remmina_utils_get_etc_release() { TRACE_CALL(__func__); gchar *etc_release = NULL; gint i; GHashTable *r; r = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); for (i = 0; distroArray[i].filename != NULL; i++) { etc_release = remmina_utils_read_distrofile(distroArray[i].filename); if (etc_release) { if (etc_release[0] != '\0') { g_hash_table_insert(r, distroArray[i].filename, etc_release); } else { g_free(etc_release); } } } return r; } /** * Print device associated with default route. * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_dev() { TRACE_CALL(__func__); gchar *dif; gint pos = 0; GString *dev; if (g_spawn_command_line_sync("ip route show default", &dif, NULL, NULL, NULL)) { dev = g_string_new(dif); g_free(dif); pos = remmina_utils_string_find(dev, pos, -1, "dev "); dev = g_string_erase(dev, 0, pos + 4); pos = remmina_utils_string_find(dev, 0, -1, " "); dev = g_string_truncate(dev, pos); return g_string_free(dev, FALSE); } return NULL; } /** * Print address associated with default route. * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_logical() { TRACE_CALL(__func__); gchar *dev = NULL; gchar *dlog; gint pos = 0; dev = remmina_utils_get_dev(); GString *lbuf = g_string_new("ip -4 -o addr show "); if ( dev != NULL ) { lbuf = g_string_append(lbuf, dev); g_free(dev); } if (g_spawn_command_line_sync(lbuf->str, &dlog, NULL, NULL, NULL)) { g_string_free(lbuf, TRUE); GString *log = g_string_new(dlog); g_free(dlog); pos = remmina_utils_string_find(log, pos, -1, "inet "); log = g_string_erase(log, 0, pos + 5); pos = remmina_utils_string_find(log, 0, -1, " "); log = g_string_truncate(log, pos); return g_string_free(log, FALSE); } g_string_free(lbuf, TRUE); return NULL; } /** * Print link associated with default route. * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_link() { TRACE_CALL(__func__); gchar *dev = NULL; gchar *plink; gint pos = 0; dev = remmina_utils_get_dev(); GString *pbuf = g_string_new("ip -4 -o link show "); if ( dev != NULL ) { pbuf = g_string_append(pbuf, dev); g_free(dev); } if (g_spawn_command_line_sync(pbuf->str, &plink, NULL, NULL, NULL)) { g_string_free(pbuf, TRUE); GString *link = g_string_new(plink); g_free(plink); pos = remmina_utils_string_find(link, pos, -1, "link/ether "); link = g_string_erase(link, 0, pos + 11); pos = remmina_utils_string_find(link, 0, -1, " "); link = g_string_truncate(link, pos); return g_string_free(link, FALSE); } g_string_free(pbuf, TRUE); return NULL; } /** * Print python version. * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_python() { TRACE_CALL(__func__); gchar *version; version = (g_spawn_command_line_sync("python -V", &version, NULL, NULL, NULL)) || (g_spawn_command_line_sync("python3 -V", &version, NULL, NULL, NULL)) ? version : NULL; if (version != NULL) version = remmina_utils_string_strip(version); return version; } /** * Print machine age. * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_utils_get_mage() { TRACE_CALL(__func__); gchar *mage = malloc(21); if (mage == NULL) { return ""; } struct stat sb; if (stat("/etc/machine-id", &sb) == 0) { sprintf(mage, "%ld", (long)(time(NULL) - sb.st_mtim.tv_sec)); } else { strcpy(mage, "0"); } return mage; } /** * Create a hexadecimal string version of the SHA-256 digest of the * buffer. * * @return a newly allocated string which the caller should free() when * finished. * * If any error occurs, this function returns NULL. */ gchar *remmina_sha256_buffer(const guchar *buffer, gssize length) { GChecksum *sha256 = NULL; gchar *digest_string = NULL; sha256 = g_checksum_new(G_CHECKSUM_SHA256); if (sha256 == NULL) { goto DONE; } g_checksum_update(sha256, buffer, length); digest_string = g_strdup(g_checksum_get_string(sha256)); DONE: if (sha256) { g_checksum_free(sha256); } return digest_string; } /** * Create a hexadecimal string version of the SHA-1 digest of the * contents of the named file. * * @return a newly allocated string which the caller * should free() when finished. * * If any error occurs while reading the file, (permission denied, * file not found, etc.), this function returns NULL. * * Taken from https://github.com/ttuegel/notmuch do PR in case of substantial modifications. * */ gchar *remmina_sha1_file(const gchar *filename) { FILE *file; #define BLOCK_SIZE 4096 unsigned char block[BLOCK_SIZE]; size_t bytes_read; GChecksum *sha1; char *digest = NULL; file = fopen(filename, "r"); if (file == NULL) return NULL; sha1 = g_checksum_new(G_CHECKSUM_SHA1); if (sha1 == NULL) goto DONE; while (1) { bytes_read = fread(block, 1, 4096, file); if (bytes_read == 0) { if (feof(file)) break; else if (ferror(file)) goto DONE; } else { g_checksum_update(sha1, block, bytes_read); } } digest = g_strdup(g_checksum_get_string(sha1)); DONE: if (sha1) g_checksum_free(sha1); if (file) fclose(file); return digest; } /** * Generate a random sting of chars to be used as part of UID for news or stats * @return a string or NULL. Caller must free it with g_free(). */ gchar *remmina_gen_random_uuid() { TRACE_CALL(__func__); gchar *result; int i; static char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; result = g_malloc0(15); if (result == NULL) { return ""; } for (i = 0; i < 7; i++) result[i] = alpha[randombytes_uniform(sizeof(alpha) - 1)]; for (i = 0; i < 7; i++) result[i + 7] = alpha[randombytes_uniform(sizeof(alpha) - 1)]; return result; } /** * Uses OSSL_DECODER_CTX to convert public key string to usable OpenSSL structs * @return an EVP_PKEY that can be used for public encryption */ EVP_PKEY *remmina_get_pubkey(const char *keytype, const char *public_key_selection) { BIO *pkbio; OSSL_DECODER_CTX *dctx; EVP_PKEY *pubkey = NULL; const char *format = "PEM"; const char *structure = NULL; dctx = OSSL_DECODER_CTX_new_for_pkey(&pubkey, format, structure, keytype, EVP_PKEY_PUBLIC_KEY, NULL, NULL); if (dctx == NULL) { return NULL; } pkbio = BIO_new_mem_buf(public_key_selection, -1); if (OSSL_DECODER_from_bio(dctx, pkbio) == 0) { BIO_free(pkbio); OSSL_DECODER_CTX_free(dctx); return NULL; } BIO_free(pkbio); OSSL_DECODER_CTX_free(dctx); return pubkey; } /** * Calls EVP_PKEY_encrypt multiple times to encrypt instr. At the end, base64 * encode the resulting buffer. * * @param pubkey an RSA public key * @param instr input string to be encrypted * * @return a buffer ptr. Use g_free() to deallocate it */ gchar *remmina_rsa_encrypt_string(EVP_PKEY *pubkey, const char *instr) { #define RSA_OAEP_PAD_SIZE 42 gchar *enc; int remaining; size_t blksz, maxblksz; unsigned char *outptr; unsigned char *ebuf = NULL; gsize ebufLen; EVP_PKEY_CTX *ctx = NULL; int pkeyLen = EVP_PKEY_size(pubkey); int inLen = strlen(instr); remaining = inLen; maxblksz = pkeyLen - RSA_OAEP_PAD_SIZE; ebufLen = (((inLen - 1) / maxblksz) + 1) * pkeyLen; ebuf = g_malloc(ebufLen); if (ebuf == NULL) { return NULL; } outptr = ebuf; ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pubkey, NULL); if (ctx == NULL) { g_free(ebuf); return NULL; } if (EVP_PKEY_encrypt_init_ex(ctx, NULL) <= 0) {; EVP_PKEY_CTX_free(ctx); g_free(ebuf); return NULL; } if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { EVP_PKEY_CTX_free(ctx); g_free(ebuf); return NULL; } /* encrypt input string block by block */ while (remaining > 0) { blksz = remaining > maxblksz ? maxblksz : remaining; size_t out_blksz; int calc_size_return_val = EVP_PKEY_encrypt(ctx, NULL, &out_blksz, (const unsigned char *)instr, blksz); if (calc_size_return_val == -2) { EVP_PKEY_CTX_free(ctx); g_free(ebuf); return NULL; } else if (calc_size_return_val <= 0) { EVP_PKEY_CTX_free(ctx); g_free(ebuf); return NULL; } if (EVP_PKEY_encrypt(ctx, outptr, &out_blksz, (const unsigned char *)instr, blksz) <= 0) {; EVP_PKEY_CTX_free(ctx); g_free(ebuf); return NULL; } instr += blksz; remaining -= blksz; outptr += out_blksz; } enc = g_base64_encode(ebuf, ebufLen); g_free(ebuf); EVP_PKEY_CTX_free(ctx); return enc; } /** * Attempts to verify the contents of @a plugin_file with base64 encoded @a signature. This function uses the OpenSSL EVP API * and expects a well-formed EC signature in DER format. The digest function used is SHA256. * * @param signature A base64 encoded signature. * @param plugin_file The file containing the message you wish to verify. * @param message_length The length of the message. * @param signature_length The length of the signature. * * @return TRUE if the signature can be verified or FALSE if it cannot or there is an error. */ gboolean remmina_verify_plugin_signature(const guchar *signature, GFile* plugin_file, size_t message_length, size_t signature_length) { EVP_PKEY *public_key = NULL; /* Get public key */ public_key = remmina_get_pubkey(RSA_KEYTYPE, remmina_RSA_PubKey_v2); if (public_key == NULL) { return FALSE; } gboolean verified = remmina_execute_plugin_signature_verification(plugin_file, message_length, signature, signature_length, public_key); EVP_PKEY_free(public_key); if (verified) { return TRUE; } else { return FALSE; } } /** * A helper function that performs the actual signature verification (that is, this function makes direct calls to the * OPENSSL EVP API) of @a plugin_file using signature @a sig with @a public_key. * * @param plugin_file The file containing the message you wish to verify. * @param msg_len The length of the message. * @param sig The signature to verify the message with. * @param sig_len The length of the signature. * @param public_key The public key to use in the signature verification. * * @return TRUE if the signature can be verified or FALSE if there is an error or the signature cannot be verified. */ gboolean remmina_execute_plugin_signature_verification(GFile* plugin_file, size_t msg_len, const guchar *sig, size_t sig_len, EVP_PKEY* public_key) { /* Returned to caller */ if(!plugin_file || !msg_len || !sig || !sig_len || !public_key) { return FALSE; } EVP_MD_CTX* ctx = NULL; gssize read = 0; gssize total_read = 0; unsigned char buffer[1025]; /* Create context for verification */ ctx = EVP_MD_CTX_create(); if(ctx == NULL) { return FALSE; } /* Returns the message digest (hash function) specified*/ const EVP_MD* message_digest = EVP_get_digestbyname(NAME_OF_DIGEST); if(message_digest == NULL) { EVP_MD_CTX_free(ctx); return FALSE; } /* Set up context with the corresponding message digest. 1 is the only code for success */ int return_val = EVP_DigestInit_ex(ctx, message_digest, NULL); if(return_val != 1) { EVP_MD_CTX_free(ctx); return FALSE; } /* Add the public key and initialize the context for verification*/ return_val = EVP_DigestVerifyInit(ctx, NULL, message_digest, NULL, public_key); if(return_val != 1) { EVP_MD_CTX_free(ctx); return FALSE; } GFileInputStream *in_stream = g_file_read(plugin_file, NULL, NULL); /* Add the msg to the context. The next step will be to actually verify the added message */ read = g_input_stream_read(G_INPUT_STREAM(in_stream), buffer, 1024, NULL, NULL); while (read > 0){ total_read += read; return_val = EVP_DigestVerifyUpdate(ctx, buffer, read); if(return_val != 1) { EVP_MD_CTX_free(ctx); return FALSE; } read = g_input_stream_read(G_INPUT_STREAM(in_stream), buffer, 1024, NULL, NULL); } msg_len = total_read; /* Clear any errors for the call below */ ERR_clear_error(); /* Execute the signature verification. */ return_val = EVP_DigestVerifyFinal(ctx, sig, sig_len); if(return_val != 1) { EVP_MD_CTX_free(ctx); return FALSE; } if(ctx != NULL) { EVP_MD_CTX_destroy(ctx); ctx = NULL; } return TRUE; } /** * * Decompresses pointer @source with length @len to file pointed to by @plugin_file. It is the caller's responsibility * to free @source and @plugin_file when no longer in use. * * @return The total number of compressed bytes read or -1 on error. */ int remmina_decompress_from_memory_to_file(guchar *source, int len, GFile* plugin_file) { GError *error = NULL; //Compress with gzip. The level is -1 for default GZlibDecompressor* gzlib_decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP); if (gzlib_decompressor == NULL) { return -1; } GInputStream *base_stream = g_memory_input_stream_new_from_data(source, len, NULL); if (base_stream == NULL) { g_object_unref(gzlib_decompressor); return -1; } GInputStream *converted_input_stream = g_converter_input_stream_new(base_stream, G_CONVERTER(gzlib_decompressor)); if (converted_input_stream == NULL) { g_object_unref(base_stream); g_object_unref(gzlib_decompressor); return -1; } int total_read = 0; GFileOutputStream *f_out_stream = g_file_replace(plugin_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); if (f_out_stream == NULL){ g_object_unref(base_stream); g_object_unref(gzlib_decompressor); return -1; } g_output_stream_splice(G_OUTPUT_STREAM(f_out_stream), converted_input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL); GFileInfo* info = g_file_query_info(plugin_file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); total_read = g_file_info_get_size(info); //Freeing stuff g_object_unref(converted_input_stream); g_object_unref(f_out_stream); g_object_unref(gzlib_decompressor); g_object_unref(base_stream); g_object_unref(info); return total_read; } /** * Compresses file @source to file @dest. Creates enough space to compress MAX_COMPRESSED_FILE_SIZE. It is the caller's * responsibility to close @source and @dest. * * @return Total number of bytes read from source or -1 on error. */ int remmina_compress_from_file_to_file(GFile *source, GFile *dest) { TRACE_CALL(__func__); GError *error = NULL; // Compress with gzip. The level is -1 for default GZlibCompressor *gzlib_compressor = g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); if (gzlib_compressor == NULL) { REMMINA_DEBUG("Compressor instantiation failed"); return -1; } GFileInputStream *base_stream = g_file_read(source, NULL, &error); if (base_stream == NULL) { REMMINA_DEBUG("Base stream is null, error: %s", error->message); g_free(error); g_object_unref(gzlib_compressor); return -1; } GInputStream *converted_input_stream = g_converter_input_stream_new((GInputStream *) base_stream, G_CONVERTER(gzlib_compressor)); if (converted_input_stream == NULL) { REMMINA_DEBUG("Failed to allocate converted input stream"); g_object_unref(base_stream); g_object_unref(gzlib_compressor); return -1; } int total_read = 0; guint8 *converted, *current_position; converted = g_malloc(sizeof(guint8) * MAX_COMPRESSED_FILE_SIZE); // 100kb file max if (converted == NULL) { REMMINA_DEBUG("Failed to allocate compressed file memory"); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); return -1; } current_position = converted; // Set traveling pointer gsize bytes_read = 0; while (total_read < MAX_COMPRESSED_FILE_SIZE) { bytes_read = g_input_stream_read(G_INPUT_STREAM(converted_input_stream), current_position, 1, NULL, &error); if (error != NULL) { REMMINA_DEBUG("Error reading input stream: %s", error->message); g_free(error); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_free(converted); return -1; } if (bytes_read == 0) { break; // Reached EOF, break read } current_position += bytes_read; total_read += bytes_read; } if (total_read >= MAX_COMPRESSED_FILE_SIZE) { REMMINA_DEBUG("Detected heap overflow in allocating compressed file"); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_free(converted); return -1; } gsize *bytes_written = g_malloc(sizeof(gsize) * total_read); if (bytes_written == NULL) { REMMINA_DEBUG("Failed to allocate bytes written"); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_free(converted); return -1; } GFileOutputStream *g_output_stream = g_file_append_to(dest, G_FILE_CREATE_NONE, NULL, &error); if (g_output_stream == NULL) { REMMINA_DEBUG("Error appending to file: %s", error->message); g_error_free(error); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_free(bytes_written); g_free(converted); return -1; } g_output_stream_write_all(G_OUTPUT_STREAM(g_output_stream), converted, total_read, bytes_written, NULL, &error); if (error != NULL) { REMMINA_DEBUG("Error writing all bytes to file: %s", error->message); g_error_free(error); g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_object_unref(g_output_stream); g_free(bytes_written); g_free(converted); return -1; } g_object_unref(base_stream); g_object_unref(gzlib_compressor); g_object_unref(converted_input_stream); g_object_unref(g_output_stream); g_free(bytes_written); g_free(converted); return total_read; }