Remmina - The GTK+ Remote Desktop Client  v1.4.33
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_file.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009-2011 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include "config.h"
38 
39 
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <sys/stat.h>
43 #include <locale.h>
44 #include <langinfo.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <time.h>
48 #include <unistd.h>
49 #include <utime.h>
50 
51 #include <gtk/gtk.h>
52 #include <glib/gi18n.h>
53 #include <glib/gstdio.h>
54 
56 #include "remmina_crypt.h"
57 #include "remmina_file_manager.h"
58 #include "remmina_log.h"
59 #include "remmina_main.h"
61 #include "remmina_plugin_manager.h"
62 #include "remmina_pref.h"
63 #include "remmina_public.h"
64 #include "remmina_sodium.h"
65 #include "remmina_utils.h"
66 
67 #define MIN_WINDOW_WIDTH 10
68 #define MIN_WINDOW_HEIGHT 10
69 
70 #define KEYFILE_GROUP_REMMINA "remmina"
71 #define KEYFILE_GROUP_STATE "Remmina Connection States"
72 
73 static struct timespec times[2];
74 
75 static RemminaFile *
77 {
78  TRACE_CALL(__func__);
79  RemminaFile *remminafile;
80 
81  remminafile = g_new0(RemminaFile, 1);
82  remminafile->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
83  remminafile->states = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
84  /* spsettings contains settings that are loaded from the secure_plugin.
85  * it’s used by remmina_file_store_secret_plugin_password() to know
86  * where to change */
87  remminafile->spsettings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
88  remminafile->prevent_saving = FALSE;
89  return remminafile;
90 }
91 
94 {
95  TRACE_CALL(__func__);
96  RemminaFile *remminafile;
97 
98  /* Try to load from the preference file for default settings first */
99  remminafile = remmina_file_load(remmina_pref_file);
100 
101  if (remminafile) {
102  g_free(remminafile->filename);
103  remminafile->filename = NULL;
104  } else {
105  remminafile = remmina_file_new_empty();
106  }
107 
108  return remminafile;
109 }
110 
115 {
116  TRACE_CALL(__func__);
117 
127  gchar *invalid_chars = "\\%|/$?<>:*. \"";
128  GString *filenamestr;
129  const gchar *s;
130 
131 
132  /* functions we can use
133  * g_strstrip( string )
134  * Removes leading and trailing whitespace from a string
135  * g_strdelimit (str, invalid_chars, '-'))
136  * Convert each invalid_chars in a hyphen
137  * g_ascii_strdown(string)
138  * all lowercase
139  * To be safe we should remove control characters as well (but I'm lazy)
140  * https://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string#C
141  * g_utf8_strncpy (gchar *dest, const gchar *src, gsize n);
142  * copies a given number of characters instead of a given number of bytes. The src string must be valid UTF-8 encoded text.
143  * g_utf8_validate (const gchar *str, gssize max_len, const gchar **end);
144  * Validates UTF-8 encoded text.
145  */
146 
147  //g_free(remminafile->filename), remminafile->filename = NULL;
148 
149  filenamestr = g_string_new(g_strdup_printf("%s",
151  if ((s = remmina_file_get_string(remminafile, "name")) == NULL) s = "name";
152  if (g_strstr_len(filenamestr->str, -1, "%N") != NULL)
153  remmina_utils_string_replace_all(filenamestr, "%N", s);
154 
155  if ((s = remmina_file_get_string(remminafile, "group")) == NULL) s = "group";
156  if (g_strstr_len(filenamestr->str, -1, "%G") != NULL)
157  remmina_utils_string_replace_all(filenamestr, "%G", s);
158 
159  if ((s = remmina_file_get_string(remminafile, "protocol")) == NULL) s = "proto";
160  if (g_strstr_len(filenamestr->str, -1, "%P") != NULL)
161  remmina_utils_string_replace_all(filenamestr, "%P", s);
162 
163  if ((s = remmina_file_get_string(remminafile, "server")) == NULL) s = "host";
164  if (g_strstr_len(filenamestr->str, -1, "%h") != NULL)
165  remmina_utils_string_replace_all(filenamestr, "%h", s);
166 
167  s = NULL;
168 
169  g_autofree gchar *filename = g_strdelimit(g_ascii_strdown(g_strstrip(g_string_free(filenamestr, FALSE)), -1),
170  invalid_chars, '-');
171 
172  GDir *dir = g_dir_open(remmina_file_get_datadir(), 0, NULL);
173 
174  if (dir != NULL)
175  remminafile->filename = g_strdup_printf("%s/%s.remmina", remmina_file_get_datadir(), filename);
176  else
177  remminafile->filename = NULL;
178  g_dir_close(dir);
179 
180 }
181 
182 void remmina_file_set_filename(RemminaFile *remminafile, const gchar *filename)
183 {
184  TRACE_CALL(__func__);
185  g_free(remminafile->filename);
186  remminafile->filename = g_strdup(filename);
187 }
188 
190 {
191  TRACE_CALL(__func__);
192 
193  if (!remminafile)
194  return;
195  else
196  g_free(remminafile->statefile);
197 
198  gchar *basename = g_path_get_basename(remminafile->filename);
199  gchar *cachedir = g_build_path("/", g_get_user_cache_dir(), "remmina", NULL);
200  GString *fname = g_string_new(basename);
201 
202  remminafile->statefile = g_strdup_printf("%s/%s.state", cachedir, fname->str);
203 
204  g_free(cachedir);
205  g_string_free(fname, TRUE);
206  g_free(basename);
207 }
208 
209 const gchar *
211 {
212  TRACE_CALL(__func__);
213  return remminafile->filename;
214 }
215 
216 RemminaFile *
217 remmina_file_copy(const gchar *filename)
218 {
219  TRACE_CALL(__func__);
220  RemminaFile *remminafile;
221  gchar *buf;
222 
223  remminafile = remmina_file_load(filename);
224  buf = g_strdup_printf( "COPY %s",
225  remmina_file_get_string(remminafile, "name"));
226  remmina_file_set_string(remminafile, "name", buf);
227  g_free(buf);
228 
229  if (remminafile)
230  remmina_file_generate_filename(remminafile);
231 
232  return remminafile;
233 }
234 
235 const RemminaProtocolSetting *find_protocol_setting(const gchar *name, RemminaProtocolPlugin *protocol_plugin)
236 {
237  TRACE_CALL(__func__);
238  const RemminaProtocolSetting *setting_iter;
239 
240  if (protocol_plugin == NULL)
241  return NULL;
242 
243  setting_iter = protocol_plugin->basic_settings;
244  if (setting_iter) {
245  while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
246  if (strcmp(name, remmina_plugin_manager_get_canonical_setting_name(setting_iter)) == 0)
247  return setting_iter;
248  setting_iter++;
249  }
250  }
251 
252  setting_iter = protocol_plugin->advanced_settings;
253  if (setting_iter) {
254  while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
255  if (strcmp(name, remmina_plugin_manager_get_canonical_setting_name(setting_iter)) == 0)
256  return setting_iter;
257  setting_iter++;
258  }
259  }
260 
261  return NULL;
262 }
263 
264 
265 static void upgrade_sshkeys_202001_mig_common_setting(RemminaFile *remminafile, gboolean protocol_is_ssh, gboolean ssh_enabled, gchar *suffix)
266 {
267  gchar *src_key;
268  gchar *dst_key;
269  const gchar *val;
270 
271  src_key = g_strdup_printf("ssh_%s", suffix);
272  dst_key = g_strdup_printf("ssh_tunnel_%s", suffix);
273 
274  val = remmina_file_get_string(remminafile, src_key);
275  if (!val) {
276  g_free(dst_key);
277  g_free(src_key);
278  return;
279  }
280 
281  if (ssh_enabled && val && val[0] != 0)
282  remmina_file_set_string(remminafile, dst_key, val);
283 
284  if (!protocol_is_ssh)
285  remmina_file_set_string(remminafile, src_key, NULL);
286 
287  g_free(dst_key);
288  g_free(src_key);
289 }
290 
291 static void upgrade_sshkeys_202001(RemminaFile *remminafile)
292 {
293  TRACE_CALL(__func__);
294 
295  gboolean protocol_is_ssh;
296  gboolean ssh_enabled;
297  const gchar *val;
298 
299  if (remmina_file_get_string(remminafile, "ssh_enabled")) {
300  /* Upgrade ssh params from remmina pre 1.4 */
301 
302  ssh_enabled = remmina_file_get_int(remminafile, "ssh_enabled", 0);
303  val = remmina_file_get_string(remminafile, "protocol");
304  protocol_is_ssh = (strcmp(val, "SSH") == 0);
305 
306  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "stricthostkeycheck");
307  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "kex_algorithms");
308  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "hostkeytypes");
309  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "ciphers");
310  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "proxycommand");
311  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "passphrase");
312  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "auth");
313  upgrade_sshkeys_202001_mig_common_setting(remminafile, protocol_is_ssh, ssh_enabled, "privatekey");
314 
315  val = remmina_file_get_string(remminafile, "ssh_loopback");
316  if (val) {
317  remmina_file_set_string(remminafile, "ssh_tunnel_loopback", val);
318  remmina_file_set_string(remminafile, "ssh_loopback", NULL);
319  }
320 
321  val = remmina_file_get_string(remminafile, "ssh_username");
322  if (val) {
323  remmina_file_set_string(remminafile, "ssh_tunnel_username", val);
324  if (protocol_is_ssh)
325  remmina_file_set_string(remminafile, "username", val);
326  remmina_file_set_string(remminafile, "ssh_username", NULL);
327  }
328 
329  val = remmina_file_get_string(remminafile, "ssh_password");
330  if (val) {
331  remmina_file_set_string(remminafile, "ssh_tunnel_password", val);
332  if (protocol_is_ssh)
333  remmina_file_set_string(remminafile, "password", val);
334  remmina_file_set_string(remminafile, "ssh_password", NULL);
335  }
336 
337  val = remmina_file_get_string(remminafile, "ssh_server");
338  if (val) {
339  remmina_file_set_string(remminafile, "ssh_tunnel_server", val);
340  remmina_file_set_string(remminafile, "ssh_server", NULL);
341  }
342 
343  /* Real key removal will be done by remmina_file_save() */
344 
345  remmina_file_set_int(remminafile, "ssh_tunnel_enabled", ssh_enabled);
346  }
347 }
348 
349 RemminaFile *
350 remmina_file_load(const gchar *filename)
351 {
352  TRACE_CALL(__func__);
353  GKeyFile *gkeyfile;
354  RemminaFile *remminafile;
355  gchar *key;
356  gchar *s;
357  RemminaProtocolPlugin *protocol_plugin;
358  RemminaSecretPlugin *secret_plugin;
359  gboolean secret_service_available;
360  int w, h;
361 
362  gkeyfile = g_key_file_new();
363 
364  if (g_file_test(filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) {
365  if (!g_key_file_load_from_file(gkeyfile, filename, G_KEY_FILE_NONE, NULL)) {
366  g_key_file_free(gkeyfile);
367  REMMINA_DEBUG("Unable to load remmina profile file %s: g_key_file_load_from_file() returned NULL.\n", filename);
368  return NULL;
369  }
370  }
371 
372  if (!g_key_file_has_key(gkeyfile, KEYFILE_GROUP_REMMINA, "name", NULL)) {
373 
374  REMMINA_DEBUG("Unable to load remmina profile file %s: cannot find key name= in section remmina.\n", filename);
375  remminafile = NULL;
376  remmina_file_set_statefile(remminafile);
377 
378  g_key_file_free(gkeyfile);
379 
380  return remminafile;
381  }
382  remminafile = remmina_file_new_empty();
383 
384  protocol_plugin = NULL;
385 
386  /* Identify the protocol plugin and get pointers to its RemminaProtocolSetting structs */
387  gchar *proto = g_key_file_get_string(gkeyfile, KEYFILE_GROUP_REMMINA, "protocol", NULL);
388  if (proto) {
390  g_free(proto);
391  }
392 
394  secret_service_available = secret_plugin && secret_plugin->is_service_available(secret_plugin);
395 
396  remminafile->filename = g_strdup(filename);
397  gsize nkeys = 0;
398  gint keyindex;
399  GError *err = NULL;
400  gchar **keys = g_key_file_get_keys(gkeyfile, KEYFILE_GROUP_REMMINA, &nkeys, &err);
401  if (keys == NULL) {
402  g_clear_error(&err);
403  }
404  for (keyindex = 0; keyindex < nkeys; ++keyindex) {
405  key = keys[keyindex];
406  /* It may contain an encrypted password
407  * - password = . // secret_service
408  * - password = $argon2id$v=19$m=262144,t=3,p=… // libsodium
409  */
410  if (protocol_plugin && remmina_plugin_manager_is_encrypted_setting(protocol_plugin, key)) {
411  s = g_key_file_get_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, NULL);
412 #if 0
413  switch (remmina_pref.enc_mode) {
417 #if SODIUM_VERSION_INT >= 90200
418 #endif
419  break;
420  case RM_ENC_MODE_GCRYPT:
421  break;
422  case RM_ENC_MODE_SECRET:
423  default:
424  break;
425  }
426 #endif
427  if ((g_strcmp0(s, ".") == 0) && (secret_service_available)) {
428  gchar *sec = secret_plugin->get_password(secret_plugin, remminafile, key);
429  remmina_file_set_string(remminafile, key, sec);
430  /* Annotate in spsettings that this value comes from secret_plugin */
431  g_hash_table_insert(remminafile->spsettings, g_strdup(key), NULL);
432  g_free(sec);
433  } else {
434  gchar *decrypted;
435  decrypted = remmina_crypt_decrypt(s);
436  remmina_file_set_string(remminafile, key, decrypted);
437  g_free(decrypted);
438  }
439  g_free(s), s = NULL;
440  } else {
441  /* If we find "resolution", then we split it in two */
442  if (strcmp(key, "resolution") == 0) {
443  gchar *resolution_str = g_key_file_get_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, NULL);
444  if (remmina_public_split_resolution_string(resolution_str, &w, &h)) {
445  gchar *buf;
446  buf = g_strdup_printf("%i", w); remmina_file_set_string(remminafile, "resolution_width", buf); g_free(buf);
447  buf = g_strdup_printf("%i", h); remmina_file_set_string(remminafile, "resolution_height", buf); g_free(buf);
448  } else {
449  remmina_file_set_string(remminafile, "resolution_width", NULL);
450  remmina_file_set_string(remminafile, "resolution_height", NULL);
451  }
452  g_free(resolution_str);
453  } else {
454  gchar *value;
455  value = g_key_file_get_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, NULL);
456  remmina_file_set_string(remminafile, key, value);
457  g_free(value);
458  }
459  }
460  }
461 
462  upgrade_sshkeys_202001(remminafile);
463  g_strfreev(keys);
464  remmina_file_set_statefile(remminafile);
465  g_key_file_free(gkeyfile);
466  return remminafile;
467 }
468 
469 void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
470 {
471  TRACE_CALL(__func__);
472 
473  /* Note: setting and value are copied on the heap, so it is responsibility of the caller
474  * to deallocate them when returning from remmina_file_set_string() if needed */
475 
477  /* Allow the execution of this function from a non main thread
478  * (plugins needs it to have user credentials)*/
480  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
481  d->func = FUNC_FILE_SET_STRING;
482  d->p.file_set_string.remminafile = remminafile;
483  d->p.file_set_string.setting = setting;
484  d->p.file_set_string.value = value;
486  g_free(d);
487  return;
488  }
489 
490  if (value) {
491  /* We refuse to accept to set the "resolution" field */
492  if (strcmp(setting, "resolution") == 0) {
493  // TRANSLATORS: This is a message that pops up when an external Remmina plugin tries to set the window resolution using a legacy parameter.
494  const gchar *message = _("Using the «resolution» parameter in the Remmina preferences file is deprecated.\n");
495  REMMINA_CRITICAL(message);
497  return;
498  }
499  g_hash_table_insert(remminafile->settings, g_strdup(setting), g_strdup(value));
500  } else {
501  g_hash_table_insert(remminafile->settings, g_strdup(setting), g_strdup(""));
502  }
503 }
504 
505 void remmina_file_set_state(RemminaFile *remminafile, const gchar *setting, const gchar *value)
506 {
507  TRACE_CALL(__func__);
508 
509  if (value && value[0] != 0)
510  g_hash_table_insert(remminafile->states, g_strdup(setting), g_strdup(value));
511  else
512  g_hash_table_insert(remminafile->states, g_strdup(setting), g_strdup(""));
513 }
514 
515 const gchar *
516 remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
517 {
518  TRACE_CALL(__func__);
519  gchar *value;
520 
521  /* Returned value is a pointer to the string stored on the hash table,
522  * please do not free it or the hash table will contain invalid pointer */
524  /* Allow the execution of this function from a non main thread
525  * (plugins needs it to have user credentials)*/
527  const gchar *retval;
528  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
529  d->func = FUNC_FILE_GET_STRING;
530  d->p.file_get_string.remminafile = remminafile;
531  d->p.file_get_string.setting = setting;
533  retval = d->p.file_get_string.retval;
534  g_free(d);
535  return retval;
536  }
537 
538  if (strcmp(setting, "resolution") == 0) {
539  // TRANSLATORS: This is a message that pop-up when an external Remmina plugin tries to set the windows resolution using a legacy parameter.
540  const gchar *message = _("Using the «resolution» parameter in the Remmina preferences file is deprecated.\n");
541  REMMINA_CRITICAL(message);
543  return NULL;
544  }
545 
546  value = (gchar *)g_hash_table_lookup(remminafile->settings, setting);
547  return value && value[0] ? value : NULL;
548 }
549 
550 gchar *
551 remmina_file_get_secret(RemminaFile *remminafile, const gchar *setting)
552 {
553  TRACE_CALL(__func__);
554 
555  /* This function is in the RemminaPluginService table, we cannot remove it
556  * without breaking plugin API */
557  g_warning("remmina_file_get_secret(remminafile,“%s”) is deprecated and must not be called. Use remmina_file_get_string() and do not deallocate returned memory.\n", setting);
558  return g_strdup(remmina_file_get_string(remminafile, setting));
559 }
560 
561 gchar *remmina_file_format_properties(RemminaFile *remminafile, const gchar *setting)
562 {
563  gchar *res = NULL;
564  GString *fmt_str;
565  GDateTime *now;
566  gchar *date_str = NULL;
567 
568  fmt_str = g_string_new(setting);
569  remmina_utils_string_replace_all(fmt_str, "%h", remmina_file_get_string(remminafile, "server"));
570  remmina_utils_string_replace_all(fmt_str, "%t", remmina_file_get_string(remminafile, "ssh_tunnel_server"));
571  remmina_utils_string_replace_all(fmt_str, "%u", remmina_file_get_string(remminafile, "username"));
572  remmina_utils_string_replace_all(fmt_str, "%U", remmina_file_get_string(remminafile, "ssh_tunnel_username"));
573  remmina_utils_string_replace_all(fmt_str, "%p", remmina_file_get_string(remminafile, "name"));
574  remmina_utils_string_replace_all(fmt_str, "%g", remmina_file_get_string(remminafile, "group"));
575 
576  now = g_date_time_new_now_local();
577  date_str = g_date_time_format(now, "%FT%TZ");
578  remmina_utils_string_replace_all(fmt_str, "%d", date_str);
579  g_free(date_str);
580 
581  res = g_string_free(fmt_str, FALSE);
582  return res;
583 }
584 
585 void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value)
586 {
587  TRACE_CALL(__func__);
588  if (remminafile)
589  g_hash_table_insert(remminafile->settings,
590  g_strdup(setting),
591  g_strdup_printf("%i", value));
592 }
593 
594 void remmina_file_set_state_int(RemminaFile *remminafile, const gchar *setting, gint value)
595 {
596  TRACE_CALL(__func__);
597  if (remminafile)
598  g_hash_table_insert(remminafile->states,
599  g_strdup(setting),
600  g_strdup_printf("%i", value));
601 }
602 
603 gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
604 {
605  TRACE_CALL(__func__);
606  gchar *value;
607  gint r;
608 
609  value = g_hash_table_lookup(remminafile->settings, setting);
610  r = value == NULL ? default_value : (value[0] == 't' ? TRUE : atoi(value));
611  // TOO verbose: REMMINA_DEBUG ("Integer value is: %d", r);
612  return r;
613 }
614 
615 gint remmina_file_get_state_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
616 {
617  TRACE_CALL(__func__);
618  gchar *value;
619  gint r;
620 
621  value = g_hash_table_lookup(remminafile->states, setting);
622  r = value == NULL ? default_value : (value[0] == 't' ? TRUE : atoi(value));
623  // TOO verbose: REMMINA_DEBUG ("Integer value is: %d", r);
624  return r;
625 }
626 
627 // sscanf uses the set language to convert the float.
628 // therefore '.' and ',' cannot be used interchangeably.
629 gdouble remmina_file_get_double(RemminaFile * remminafile,
630  const gchar * setting,
631  gdouble default_value)
632 {
633  TRACE_CALL(__func__);
634  gchar *value;
635 
636  value = g_hash_table_lookup(remminafile->settings, setting);
637  if (!value)
638  return default_value;
639 
640  // str to double.
641  // https://stackoverflow.com/questions/10075294/converting-string-to-a-double-variable-in-c
642  gdouble d;
643  gint ret = sscanf(value, "%lf", &d);
644 
645  if (ret != 1)
646  // failed.
647  d = default_value;
648 
649  // TOO VERBOSE: REMMINA_DEBUG("Double value is: %lf", d);
650  return d;
651 }
652 
653 // sscanf uses the set language to convert the float.
654 // therefore '.' and ',' cannot be used interchangeably.
656  const gchar * setting,
657  gdouble default_value)
658 {
659  TRACE_CALL(__func__);
660  gchar *value;
661 
662  value = g_hash_table_lookup(remminafile->states, setting);
663  if (!value)
664  return default_value;
665 
666  // str to double.
667  // https://stackoverflow.com/questions/10075294/converting-string-to-a-double-variable-in-c
668  gdouble d;
669  gint ret = sscanf(value, "%lf", &d);
670 
671  if (ret != 1)
672  // failed.
673  d = default_value;
674 
675  // TOO VERBOSE: REMMINA_DEBUG("Double value is: %lf", d);
676  return d;
677 }
678 
679 static GKeyFile *
681 {
682  TRACE_CALL(__func__);
683  GKeyFile *gkeyfile;
684 
685  if (remminafile->filename == NULL)
686  return NULL;
687  gkeyfile = g_key_file_new();
688  if (!g_key_file_load_from_file(gkeyfile, remminafile->filename, G_KEY_FILE_NONE, NULL)) {
689  /* it will fail if it’s a new file, but shouldn’t matter. */
690  }
691  return gkeyfile;
692 }
693 
694 static GKeyFile *
696 {
697  TRACE_CALL(__func__);
698  GKeyFile *gkeyfile;
699 
700  if (remminafile->statefile == NULL)
701  return NULL;
702  gkeyfile = g_key_file_new();
703  if (!g_key_file_load_from_file(gkeyfile, remminafile->statefile, G_KEY_FILE_NONE, NULL)) {
704  /* it will fail if it’s a new file, but shouldn’t matter. */
705  }
706  return gkeyfile;
707 }
708 
709 void remmina_file_free(RemminaFile *remminafile)
710 {
711  TRACE_CALL(__func__);
712  if (remminafile == NULL)
713  return;
714 
715  if (remminafile->filename)
716  g_free(remminafile->filename);
717  if (remminafile->statefile)
718  g_free(remminafile->statefile);
719  if (remminafile->settings)
720  g_hash_table_destroy(remminafile->settings);
721  if (remminafile->spsettings)
722  g_hash_table_destroy(remminafile->spsettings);
723  if (remminafile->states)
724  g_hash_table_destroy(remminafile->states);
725 
726  g_free(remminafile);
727 }
728 
729 
730 void remmina_file_save(RemminaFile *remminafile)
731 {
732  TRACE_CALL(__func__);
733  RemminaSecretPlugin *secret_plugin;
734  gboolean secret_service_available;
735  RemminaProtocolPlugin *protocol_plugin;
736  GHashTableIter iter;
737  const gchar *key, *value;
738  gchar *s, *proto, *content;
739  gint nopasswdsave;
740  GKeyFile *gkeyfile;
741  GKeyFile *gkeystate;
742  gsize length = 0;
743  GError *err = NULL;
744 
745  if (remminafile->prevent_saving)
746  return;
747 
748  if ((gkeyfile = remmina_file_get_keyfile(remminafile)) == NULL)
749  return;
750 
751  if ((gkeystate = remmina_file_get_keystate(remminafile)) == NULL)
752  return;
753 
754  REMMINA_DEBUG("Saving profile");
755  /* get disablepasswordstoring */
756  nopasswdsave = remmina_file_get_int(remminafile, "disablepasswordstoring", 0);
757  /* Identify the protocol plugin and get pointers to its RemminaProtocolSetting structs */
758  proto = (gchar *)g_hash_table_lookup(remminafile->settings, "protocol");
759  if (proto) {
761  } else {
762  REMMINA_CRITICAL("Saving settings for unknown protocol:", proto);
763  protocol_plugin = NULL;
764  }
765 
767  secret_service_available = secret_plugin && secret_plugin->is_service_available(secret_plugin);
768 
769  g_hash_table_iter_init(&iter, remminafile->settings);
770  while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) {
771  if (remmina_plugin_manager_is_encrypted_setting(protocol_plugin, key)) {
772  if (remminafile->filename && g_strcmp0(remminafile->filename, remmina_pref_file)) {
773  if (secret_service_available && nopasswdsave == 0) {
774  REMMINA_DEBUG("We have a secret and disablepasswordstoring=0");
775  if (value && value[0]) {
776  if (g_strcmp0(value, ".") != 0)
777  secret_plugin->store_password(secret_plugin, remminafile, key, value);
778  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, ".");
779  } else {
780  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, "");
781  secret_plugin->delete_password(secret_plugin, remminafile, key);
782  }
783  } else {
784  REMMINA_DEBUG("We have a password and disablepasswordstoring=0");
785  if (value && value[0] && nopasswdsave == 0) {
786  s = remmina_crypt_encrypt(value);
787  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, s);
788  g_free(s);
789  } else {
790  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, "");
791  }
792  }
793  if (secret_service_available && nopasswdsave == 1) {
794  if (value && value[0]) {
795  if (g_strcmp0(value, ".") != 0) {
796  REMMINA_DEBUG("Deleting the secret in the keyring as disablepasswordstoring=1");
797  secret_plugin->delete_password(secret_plugin, remminafile, key);
798  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, ".");
799  }
800  }
801  }
802  }
803  } else {
804  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, value);
805  }
806  }
807 
808  /* Avoid storing redundant and deprecated "resolution" field */
809  g_key_file_remove_key(gkeyfile, KEYFILE_GROUP_REMMINA, "resolution", NULL);
810 
811  /* Delete old pre-1.4 ssh keys */
812  g_key_file_remove_key(gkeyfile, KEYFILE_GROUP_REMMINA, "ssh_enabled", NULL);
813  g_key_file_remove_key(gkeyfile, KEYFILE_GROUP_REMMINA, "save_ssh_server", NULL);
814  g_key_file_remove_key(gkeyfile, KEYFILE_GROUP_REMMINA, "save_ssh_username", NULL);
815 
816  /* Store gkeyfile to disk (password are already sent to keyring) */
817  content = g_key_file_to_data(gkeyfile, &length, NULL);
818 
819  if (g_file_set_contents(remminafile->filename, content, length, &err))
820  REMMINA_DEBUG("Profile saved");
821  else
822  REMMINA_WARNING("Remmina connection profile cannot be saved, with error %d (%s)", err->code, err->message);
823  if (err != NULL)
824  g_error_free(err);
825 
826  g_free(content), content = NULL;
827  /* Saving states */
828  g_hash_table_iter_init(&iter, remminafile->states);
829  while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value))
830  g_key_file_set_string(gkeyfile, KEYFILE_GROUP_STATE, key, value);
831  content = g_key_file_to_data(gkeystate, &length, NULL);
832  if (g_file_set_contents(remminafile->statefile, content, length, &err))
833  REMMINA_DEBUG("Connection profile states saved");
834  else
835  REMMINA_WARNING("Remmina connection profile cannot be saved, with error %d (%s)", err->code, err->message);
836  if (err != NULL)
837  g_error_free(err);
838  g_free(content), content = NULL;
839  g_key_file_free(gkeyfile);
840  g_key_file_free(gkeystate);
841 
844 }
845 
846 void remmina_file_store_secret_plugin_password(RemminaFile *remminafile, const gchar *key, const gchar *value)
847 {
848  TRACE_CALL(__func__);
849 
850  /* Only change the password in the keyring. This function
851  * is a shortcut which avoids updating of date/time of .pref file
852  * when possible, and is used by the mpchanger */
853  RemminaSecretPlugin *plugin;
854 
855  if (g_hash_table_lookup_extended(remminafile->spsettings, g_strdup(key), NULL, NULL)) {
857  plugin->store_password(plugin, remminafile, key, value);
858  } else {
859  remmina_file_set_string(remminafile, key, value);
860  remmina_file_save(remminafile);
861  }
862 }
863 
864 RemminaFile *
866 {
867  TRACE_CALL(__func__);
868  RemminaFile *dupfile;
869  GHashTableIter iter;
870  const gchar *key, *value;
871 
872  dupfile = remmina_file_new_empty();
873  dupfile->filename = g_strdup(remminafile->filename);
874 
875  g_hash_table_iter_init(&iter, remminafile->settings);
876  while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value))
877  remmina_file_set_string(dupfile, key, value);
878 
880  remmina_file_touch(dupfile);
881  return dupfile;
882 }
883 
884 const gchar *
886 {
887  TRACE_CALL(__func__);
888  RemminaProtocolPlugin *plugin;
889 
891  remmina_file_get_string(remminafile, "protocol"));
892  if (!plugin)
893  return g_strconcat(REMMINA_APP_ID, "-symbolic", NULL);
894 
895  return remmina_file_get_int(remminafile, "ssh_tunnel_enabled", FALSE) ? plugin->icon_name_ssh : plugin->icon_name;
896 }
897 
898 RemminaFile *
899 remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol)
900 {
901  TRACE_CALL(__func__);
902  RemminaFile *tmp;
903 
904  tmp = remmina_file_dup(remminafile);
905  g_free(tmp->filename);
906  tmp->filename = NULL;
907  remmina_file_set_string(tmp, "protocol", new_protocol);
908  return tmp;
909 }
910 
911 void remmina_file_delete(const gchar *filename)
912 {
913  TRACE_CALL(__func__);
914  RemminaFile *remminafile;
915 
916  remminafile = remmina_file_load(filename);
917  if (remminafile) {
918  remmina_file_unsave_passwords(remminafile);
919  remmina_file_free(remminafile);
920  }
921  g_unlink(filename);
922 }
923 
924 const gchar *
925 remmina_file_get_state(RemminaFile *remminafile, const gchar *setting)
926 {
927  TRACE_CALL(__func__);
928  g_autoptr(GError) error = NULL;
929  g_autoptr(GKeyFile) key_file = g_key_file_new();
930 
931  if (!g_key_file_load_from_file(key_file, remminafile->statefile, G_KEY_FILE_NONE, &error)) {
932  if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
933  REMMINA_CRITICAL("Could not load the state file. %s", error->message);
934  return NULL;
935  }
936 
937  g_autofree gchar *val = g_key_file_get_string(key_file, KEYFILE_GROUP_STATE, setting, &error);
938 
939  if (val == NULL &&
940  !g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
941  REMMINA_CRITICAL("Could not find \"%s\" in the \"%s\" state file. %s",
942  setting, remminafile->statefile, error->message);
943  return NULL;
944  }
945  return val && val[0] ? val : NULL;
946 }
947 
949 {
950  TRACE_CALL(__func__);
951 
952  g_autoptr(GKeyFile) key_statefile = g_key_file_new();
953  g_autoptr(GKeyFile) key_remminafile = g_key_file_new();
954  GError *error = NULL;
955 
956  const gchar *date = NULL;
957  GDateTime *d = g_date_time_new_now_utc();
958 
959  date = g_strdup_printf("%d%02d%02d",
960  g_date_time_get_year(d),
961  g_date_time_get_month(d),
962  g_date_time_get_day_of_month(d));
963 
964  g_key_file_set_string(key_statefile, KEYFILE_GROUP_STATE, "last_success", date);
965 
966  REMMINA_DEBUG("State file %s.", remminafile->statefile);
967  if (!g_key_file_save_to_file(key_statefile, remminafile->statefile, &error)) {
968  REMMINA_CRITICAL("Could not save the key file. %s", error->message);
969  g_error_free(error);
970  error = NULL;
971  return;
972  }
973  /* Delete old pre-1.5 keys */
974  g_key_file_remove_key(key_remminafile, KEYFILE_GROUP_REMMINA, "last_success", NULL);
975  REMMINA_DEBUG("Last connection made on %s.", date);
976 }
977 
979 {
980  /* Delete all saved secrets for this profile */
981 
982  TRACE_CALL(__func__);
983  const RemminaProtocolSetting *setting_iter;
984  RemminaProtocolPlugin *protocol_plugin;
985  gchar *proto;
986 
987  protocol_plugin = NULL;
988 
989  remmina_file_set_string(remminafile, "password", NULL);
990 
991  proto = (gchar *)g_hash_table_lookup(remminafile->settings, "protocol");
992  if (proto) {
994  if (protocol_plugin) {
995  setting_iter = protocol_plugin->basic_settings;
996  if (setting_iter) {
997  while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
998  // TOO VERBOSE: g_debug("setting name: %s", setting_iter->name);
999  if (setting_iter->name == NULL)
1000  g_error("Internal error: a setting name in protocol plugin %s is null. Please fix RemminaProtocolSetting struct content.", proto);
1001  else
1002  if (remmina_plugin_manager_is_encrypted_setting(protocol_plugin, setting_iter->name))
1004  setting_iter++;
1005  }
1006  }
1007  setting_iter = protocol_plugin->advanced_settings;
1008  if (setting_iter) {
1009  while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
1010  if (remmina_plugin_manager_is_encrypted_setting(protocol_plugin, setting_iter->name))
1012  setting_iter++;
1013  }
1014  }
1015  remmina_file_save(remminafile);
1016  }
1017  }
1018 }
1019 
1030 gchar *
1032 {
1033  TRACE_CALL(__func__);
1034 
1035  GFile *file;
1036  GFileInfo *info;
1037 
1038  struct timeval tv;
1039  struct tm *ptm;
1040  char time_string[256];
1041  gchar *tmps;
1042 
1043  guint64 mtime;
1044 
1045  if (remminafile->statefile)
1046  //REMMINA_DEBUG ("remminafile->statefile: %s", remminafile->statefile);
1047  file = g_file_new_for_path(remminafile->statefile);
1048  else
1049  file = g_file_new_for_path(remminafile->filename);
1050 
1051  info = g_file_query_info(file,
1052  G_FILE_ATTRIBUTE_TIME_MODIFIED,
1053  G_FILE_QUERY_INFO_NONE,
1054  NULL,
1055  NULL);
1056 
1057  g_object_unref(file);
1058 
1059  if (info == NULL) {
1060  //REMMINA_DEBUG("could not get time info");
1061 
1062  // The BDAY "Fri, 16 Oct 2009 07:04:46 GMT"
1063  mtime = 1255676686;
1064  const gchar *last_success = remmina_file_get_string(remminafile, "last_success");
1065  if (last_success) {
1066  //REMMINA_DEBUG ("Last success is %s", last_success);
1067  GDateTime *dt;
1068  tmps = g_strconcat(last_success, "T00:00:00Z", NULL);
1069  dt = g_date_time_new_from_iso8601(tmps, NULL);
1070  g_free(tmps);
1071  if (dt) {
1072  //REMMINA_DEBUG("Converting last_success");
1073  tmps = g_date_time_format(dt, "%s");
1074  mtime = g_ascii_strtoull(tmps, NULL, 10);
1075  g_free(tmps);
1076  g_date_time_unref(dt);
1077  } else {
1078  //REMMINA_DEBUG("dt was null");
1079  mtime = 191543400;
1080  }
1081  }
1082  } else {
1083  mtime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1084  g_object_unref(info);
1085  }
1086 
1087  tv.tv_sec = mtime;
1088 
1089  ptm = localtime(&tv.tv_sec);
1090  strftime(time_string, sizeof(time_string), "%F - %T", ptm);
1091 
1092  gchar *modtime_string = g_locale_to_utf8(time_string, -1, NULL, NULL, NULL);
1093 
1094  return modtime_string;
1095 }
1096 
1104 void
1106 {
1107  TRACE_CALL(__func__);
1108  int fd;
1109  struct stat st;
1110  int r;
1111 
1112  if ((r = stat(remminafile->statefile, &st)) < 0) {
1113  if (errno != ENOENT)
1114  REMMINA_DEBUG("stat %s:", remminafile->statefile);
1115  } else if (!r) {
1116 #ifdef __APPLE__
1117  times[0] = st.st_atimespec;
1118  times[1] = st.st_mtimespec;
1119 #else
1120  times[0] = st.st_atim;
1121  times[1] = st.st_mtim;
1122 #endif
1123  if (utimensat(AT_FDCWD, remminafile->statefile, times, 0) < 0)
1124  REMMINA_DEBUG("utimensat %s:", remminafile->statefile);
1125  return;
1126  }
1127 
1128  if ((fd = open(remminafile->statefile, O_CREAT | O_EXCL, 0644)) < 0)
1129  REMMINA_DEBUG("open %s:", remminafile->statefile);
1130  close(fd);
1131 
1132  remmina_file_touch(remminafile);
1133 }
RemminaFile * remmina_file_load(const gchar *filename)
Definition: remmina_file.c:350
const gchar * remmina_plugin_manager_get_canonical_setting_name(const RemminaProtocolSetting *setting)
void remmina_file_free(RemminaFile *remminafile)
Definition: remmina_file.c:709
gboolean remmina_plugin_manager_is_encrypted_setting(RemminaProtocolPlugin *pp, const char *setting)
const gchar * remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:516
RemminaFile * remmina_file_dup(RemminaFile *remminafile)
Definition: remmina_file.c:865
gchar * remmina_pref_file
Definition: rcw.c:78
const gchar * remmina_file_get_filename(RemminaFile *remminafile)
Definition: remmina_file.c:210
gchar * remmina_crypt_decrypt(const gchar *str)
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
static struct timespec times[2]
Definition: remmina_file.c:73
RemminaFile * remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol)
Definition: remmina_file.c:899
static GKeyFile * remmina_file_get_keystate(RemminaFile *remminafile)
Definition: remmina_file.c:695
gchar * remmina_file_get_secret(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:551
RemminaSecretPlugin * remmina_plugin_manager_get_secret_plugin(void)
gboolean(* is_service_available)(struct _RemminaSecretPlugin *instance)
Definition: plugin.h:144
void(* delete_password)(struct _RemminaSecretPlugin *instance, RemminaFile *remminafile, const gchar *key)
Definition: plugin.h:147
gchar * remmina_file_get_datetime(RemminaFile *remminafile)
Return the string date of the last time a Remmina state file has been modified.
static void upgrade_sshkeys_202001(RemminaFile *remminafile)
Definition: remmina_file.c:291
gdouble remmina_file_get_double(RemminaFile *remminafile, const gchar *setting, gdouble default_value)
Definition: remmina_file.c:629
void remmina_file_generate_filename(RemminaFile *remminafile)
Generate a new Remmina connection profile file name.
Definition: remmina_file.c:114
const gchar * remmina_file_get_state(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:925
int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
gchar * remmina_file_format_properties(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:561
const RemminaProtocolSetting * find_protocol_setting(const gchar *name, RemminaProtocolPlugin *protocol_plugin)
Definition: remmina_file.c:235
void remmina_main_update_file_datetime(RemminaFile *file)
const gchar * icon_name_ssh
Definition: plugin.h:73
gint remmina_file_get_state_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:615
void remmina_file_state_last_success(RemminaFile *remminafile)
Definition: remmina_file.c:948
General utility functions, non-GTK related.
void remmina_file_set_statefile(RemminaFile *remminafile)
Definition: remmina_file.c:189
void remmina_file_set_filename(RemminaFile *remminafile, const gchar *filename)
Definition: remmina_file.c:182
gboolean remmina_masterthread_exec_is_main_thread()
gchar * remmina_crypt_encrypt(const gchar *str)
Definition: remmina_crypt.c:93
gdouble remmina_file_get_state_double(RemminaFile *remminafile, const gchar *setting, gdouble default_value)
Definition: remmina_file.c:655
void remmina_main_show_warning_dialog(const gchar *message)
void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value)
Definition: remmina_file.c:585
static GKeyFile * remmina_file_get_keyfile(RemminaFile *remminafile)
Definition: remmina_file.c:680
void remmina_file_delete(const gchar *filename)
Definition: remmina_file.c:911
const RemminaProtocolSetting * basic_settings
Definition: plugin.h:74
struct remmina_masterthread_exec_data::@12::@17 file_set_string
struct remmina_masterthread_exec_data::@12::@16 file_get_string
gchar * remmina_file_get_datadir(void)
Return datadir_path from pref or first found data dir as per XDG specs.
void remmina_file_touch(RemminaFile *remminafile)
Update the atime and mtime of a given filename.
static gchar * cachedir
gboolean list_refresh_workaround
Definition: remmina_pref.h:150
RemminaProtocolSettingType type
Definition: types.h:118
void remmina_masterthread_exec_and_wait(RemminaMTExecData *d)
RemminaFile * remmina_file_new(void)
Definition: remmina_file.c:93
RemminaPref remmina_pref
Definition: rcw.c:79
const gchar * remmina_file_name
Definition: remmina_pref.h:136
void remmina_file_set_state(RemminaFile *remminafile, const gchar *setting, const gchar *value)
Definition: remmina_file.c:505
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
Replaces all occurrences of needle in haystack with replace.
void remmina_file_store_secret_plugin_password(RemminaFile *remminafile, const gchar *key, const gchar *value)
Definition: remmina_file.c:846
RemminaPlugin * remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name)
const RemminaProtocolSetting * advanced_settings
Definition: plugin.h:75
union remmina_masterthread_exec_data::@12 p
gchar *(* get_password)(struct _RemminaSecretPlugin *instance, RemminaFile *remminafile, const gchar *key)
Definition: plugin.h:146
void(* store_password)(struct _RemminaSecretPlugin *instance, RemminaFile *remminafile, const gchar *key, const gchar *password)
Definition: plugin.h:145
void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
Definition: remmina_file.c:469
void remmina_file_save(RemminaFile *remminafile)
Definition: remmina_file.c:730
RemminaFile * remmina_file_copy(const gchar *filename)
Definition: remmina_file.c:217
static void upgrade_sshkeys_202001_mig_common_setting(RemminaFile *remminafile, gboolean protocol_is_ssh, gboolean ssh_enabled, gchar *suffix)
Definition: remmina_file.c:265
const gchar * icon_name
Definition: plugin.h:72
void remmina_file_unsave_passwords(RemminaFile *remminafile)
Definition: remmina_file.c:978
void remmina_file_set_state_int(RemminaFile *remminafile, const gchar *setting, gint value)
Definition: remmina_file.c:594
const gchar * remmina_file_get_icon_name(RemminaFile *remminafile)
Definition: remmina_file.c:885
static RemminaFile * remmina_file_new_empty(void)
Definition: remmina_file.c:76
enum remmina_masterthread_exec_data::@11 func
const gchar * name
Definition: types.h:119