Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
configure.ac | 1 +
libreport.spec.in | 7 -
src/gtk-helpers/Makefile.am | 6 +-
src/gtk-helpers/abrt-keyring.c | 130 ----
src/gtk-helpers/event_config_dialog.c | 71 +--
src/gtk-helpers/internal_libreport_gtk.h | 15 +-
src/gtk-helpers/secrets.c | 1053 ++++++++++++++++++++++++++++++
src/gui-wizard-gtk/main.c | 2 +-
8 files changed, 1075 insertions(+), 210 deletions(-)
delete mode 100644 src/gtk-helpers/abrt-keyring.c
create mode 100644 src/gtk-helpers/secrets.c
diff --git a/configure.ac b/configure.ac
index 25d44e5..06896a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -82,6 +82,7 @@ AC_SUBST(XMLRPC_CLIENT_LIBS)
PKG_CHECK_MODULES([GTK], [gtk+-3.0])
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.21])
+PKG_CHECK_MODULES([GIO], [gio-2.0])
PKG_CHECK_MODULES([DBUS], [dbus-1])
PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
PKG_CHECK_MODULES([NSS], [nss])
diff --git a/libreport.spec.in b/libreport.spec.in
index f65c87d..ebe247a 100644
--- a/libreport.spec.in
+++ b/libreport.spec.in
@@ -35,13 +35,6 @@ Requires: libreport-filesystem
# FIXME: can be removed when F15 will EOLed, needs to stay in rhel6!
Requires: libreport-python
-# for rhel6
-%if 0%{?rhel} >= 6
-BuildRequires: gnome-keyring-devel
-%else
-BuildRequires: libgnome-keyring-devel
-%endif
-
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%description
diff --git a/src/gtk-helpers/Makefile.am b/src/gtk-helpers/Makefile.am
index 188e48f..68ec928 100644
--- a/src/gtk-helpers/Makefile.am
+++ b/src/gtk-helpers/Makefile.am
@@ -10,7 +10,7 @@ lib_LTLIBRARIES = \
libreport_gtk_la_SOURCES = \
event_config_dialog.c \
- abrt-keyring.c \
+ secrets.c \
hyperlinks.c \
autowrapped_label.c
@@ -20,14 +20,14 @@ libreport_gtk_la_CPPFLAGS = \
-I$(srcdir)/../lib \
$(GTK_CFLAGS) \
$(GLIB_CFLAGS) \
- $(GNOME_KEYRING_CFLAGS) \
+ $(GIO_CFLAGS) \
-D_GNU_SOURCE
libreport_gtk_la_LDFLAGS = \
-version-info 0:1:0
libreport_gtk_la_LIBADD = \
$(GTK_LIBS) \
$(GLIB_LIBS) \
- $(GNOME_KEYRING_LIBS) \
+ $(GIO_LIBS) \
../lib/libreport.la
EXTRA_DIST = libreport-gtk.pc.in
diff --git a/src/gtk-helpers/abrt-keyring.c b/src/gtk-helpers/abrt-keyring.c
deleted file mode 100644
index 4e830ca..0000000
--- a/src/gtk-helpers/abrt-keyring.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- Copyright (C) 2011 ABRT Team
- Copyright (C) 2011 RedHat inc.
-
- 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.
-*/
-#include <gnome-keyring.h>
-#include "internal_libreport.h"
-#include "internal_libreport_gtk.h"
-
-static char *keyring_name;
-static bool got_keyring = 0;
-bool g_keyring_available = 1; //by default we assume that keyring is available
-
-guint32 find_keyring_item_id_for_event(const char *event_name)
-{
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- GList *found = NULL;
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig",
event_name);
- GnomeKeyringResult result = gnome_keyring_find_items_sync(
- GNOME_KEYRING_ITEM_GENERIC_SECRET,
- attrs,
- &found);
- gnome_keyring_attribute_list_free(attrs);
-
- //let's hope 0 is not valid item_id
- guint32 item_id = 0;
- if (result != GNOME_KEYRING_RESULT_OK)
- goto ret;
- if (found)
- item_id = ((GnomeKeyringFound *)found->data)->item_id;
- ret:
- if (found)
- gnome_keyring_found_list_free(found);
- VERB2 log("keyring has %sconfiguration for event '%s'", (item_id !=
0) ? "" : "no ", event_name);
- return item_id;
-}
-
-static void abrt_keyring_load_settings(const char *event_name, event_config_t *ec)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
-
- guint item_id = find_keyring_item_id_for_event(event_name);
- if (!item_id)
- return;
- GnomeKeyringAttributeList *attrs = NULL;
- GnomeKeyringResult result = gnome_keyring_item_get_attributes_sync(
- keyring_name,
- item_id,
- &attrs);
- if (result == GNOME_KEYRING_RESULT_OK && attrs)
- {
- VERB3 log("num attrs %i", attrs->len);
- guint index;
- for (index = 0; index < attrs->len; index++)
- {
- char *name = g_array_index(attrs, GnomeKeyringAttribute, index).name;
- VERB2 log("keyring has name '%s'", name);
- event_option_t *option = get_event_option_from_list(name, ec->options);
- if (option)
- {
- free(option->eo_value);
- option->eo_value = xstrdup(g_array_index(attrs, GnomeKeyringAttribute,
index).value.string);
- VERB2 log("added or replaced in event config:'%s=%s'",
name, option->eo_value);
- }
- }
- }
- if (attrs)
- gnome_keyring_attribute_list_free(attrs);
-}
-
-static void init_keyring()
-{
- /* Called again? */
- if (got_keyring || !g_keyring_available)
- return;
-
- if (!gnome_keyring_is_available())
- {
- g_keyring_available = 0;
- VERB1 error_msg("Cannot connect to Gnome keyring daemon");
- return;
- }
-
- GnomeKeyringResult result =
gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- got_keyring = 1;
- /*
- * Note: The default keyring name can be NULL. It is a valid name.
- */
- VERB2 log("keyring:'%s'", keyring_name);
-}
-
-static void load_event_config(gpointer key, gpointer value, gpointer user_data)
-{
- char* event_name = (char*)key;
- event_config_t *ec = (event_config_t *)value;
- VERB2 log("loading event '%s' configuration from keyring",
event_name);
- abrt_keyring_load_settings(event_name, ec);
-}
-
-/*
- * Tries to load settings for all events in g_event_config_list
-*/
-void load_event_config_data_from_keyring(void)
-{
- init_keyring();
- if (!got_keyring)
- return;
- g_hash_table_foreach(g_event_config_list, &load_event_config, NULL);
-}
diff --git a/src/gtk-helpers/event_config_dialog.c
b/src/gtk-helpers/event_config_dialog.c
index 5a564fe..190dad1 100644
--- a/src/gtk-helpers/event_config_dialog.c
+++ b/src/gtk-helpers/event_config_dialog.c
@@ -17,7 +17,6 @@
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <gtk/gtk.h>
-#include <gnome-keyring.h>
#include "internal_libreport_gtk.h"
static GtkWindow *g_event_list_window;
@@ -298,63 +297,6 @@ static void dehydrate_config_dialog()
g_list_foreach(option_widget_list, &save_value_from_widget, NULL);
}
-static void save_settings_to_keyring(const char *event_name)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
- char *keyring_name = NULL;
- GnomeKeyringResult result =
gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- const char *store_passwords_s = get_user_setting("store_passwords");
- bool store_passwords = !(store_passwords_s && !strcmp(store_passwords_s,
"no"));
-
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- event_config_t *ec = get_event_config(event_name);
- /* add string id which we use to search for items */
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig",
event_name);
- GList *l;
- for (l = g_list_first(ec->options); l != NULL; l = g_list_next(l))
- {
- event_option_t *op = (event_option_t *)l->data;
- gnome_keyring_attribute_list_append_string(attrs, op->eo_name,
- (!store_passwords && op->eo_type == OPTION_TYPE_PASSWORD) ?
"" : op->eo_value);
- }
-
- guint32 item_id = find_keyring_item_id_for_event(event_name);
- if (item_id)
- {
- /* found existing item, so just update the values */
- result = gnome_keyring_item_set_attributes_sync(keyring_name, item_id, attrs);
- VERB2 log("updated item with id: %i", item_id);
- }
- else
- {
- /* did't find existing item, so create a new one */
- result = gnome_keyring_item_create_sync(keyring_name,
- GNOME_KEYRING_ITEM_GENERIC_SECRET, /* type */
- event_name, /* display name */
- attrs, /* attributes */
- "", /* secret - no special handling for
password it's stored in attrs */
- 1, /* update if exist */
- &item_id);
- VERB2 log("created new item with id: %i", item_id);
- }
- gnome_keyring_attribute_list_free(attrs);
-
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Error saving event '%s' configuration to keyring",
event_name);
- return;
- }
- VERB2 log("saved event '%s' configuration to keyring",
event_name);
-}
-
int show_event_config_dialog(const char *event_name, GtkWindow *parent)
{
if (option_widget_list != NULL)
@@ -425,14 +367,14 @@ int show_event_config_dialog(const char *event_name, GtkWindow
*parent)
if (g_list_length(gtk_container_get_children(GTK_CONTAINER(g_adv_option_table))) >
0)
gtk_box_pack_start(GTK_BOX(content), adv_expander, false, false, 0);
- /* add warning if keyring is not available showing the nagging dialog
+ /* add warning if secrets service is not available showing the nagging dialog
* is considered "too heavy UI" be designers
*/
- if (!g_keyring_available)
+ if (!is_event_config_user_storage_available())
{
GtkWidget *keyring_warn_lbl =
gtk_label_new(
- _("Gnome Keyring is not available, your settings won't be
saved!"));
+ _("Secret Service is not available, your settings won't be
saved!"));
static const GdkColor red = { .red = 0xffff };
gtk_widget_modify_fg(keyring_warn_lbl, GTK_STATE_NORMAL, &red);
gtk_box_pack_start(GTK_BOX(content), keyring_warn_lbl, false, false, 0);
@@ -444,7 +386,10 @@ int show_event_config_dialog(const char *event_name, GtkWindow
*parent)
if (result == GTK_RESPONSE_APPLY)
{
dehydrate_config_dialog();
- save_settings_to_keyring(event_name);
+ const char *const store_passwords_s =
get_user_setting("store_passwords");
+ save_event_config_data_to_user_storage(event_name,
+ get_event_config(event_name),
+ !(store_passwords_s &&
!strcmp(store_passwords_s, "no")));
}
//else if (result == GTK_RESPONSE_CANCEL)
// log("log");
@@ -460,7 +405,7 @@ void show_events_list_dialog(GtkWindow *parent)
if (g_event_config_list == NULL)
{
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
}
GtkWidget *event_list_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
diff --git a/src/gtk-helpers/internal_libreport_gtk.h
b/src/gtk-helpers/internal_libreport_gtk.h
index 092bfad..dd9ff87 100644
--- a/src/gtk-helpers/internal_libreport_gtk.h
+++ b/src/gtk-helpers/internal_libreport_gtk.h
@@ -27,19 +27,22 @@
extern "C" {
#endif
-extern bool g_keyring_available;
-
#define make_label_autowrap_on_resize libreport_make_label_autowrap_on_resize
void make_label_autowrap_on_resize(GtkLabel *label);
#define show_events_list_dialog libreport_show_events_list_dialog
void show_events_list_dialog(GtkWindow *parent);
-#define load_event_config_data_from_keyring
libreport_load_event_config_data_from_keyring
-void load_event_config_data_from_keyring(void);
+#define is_event_config_user_storage_available
libreport_is_event_config_user_storage_available
+bool is_event_config_user_storage_available();
+
+#define load_event_config_data_from_user_storage
libreport_load_event_config_data_from_user_storage
+void load_event_config_data_from_user_storage(GHashTable *event_config_list);
-#define find_keyring_item_id_for_event libreport_find_keyring_item_id_for_event
-guint32 find_keyring_item_id_for_event(const char *event_name);
+#define save_event_config_data_to_user_storage
libreport_save_event_config_data_to_user_storage
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_password);
#define show_event_config_dialog libreport_show_event_config_dialog
int show_event_config_dialog(const char *event_name, GtkWindow *parent);
diff --git a/src/gtk-helpers/secrets.c b/src/gtk-helpers/secrets.c
new file mode 100644
index 0000000..db96bed
--- /dev/null
+++ b/src/gtk-helpers/secrets.c
@@ -0,0 +1,1053 @@
+/*
+ Copyright (C) 2012 ABRT Team
+ Copyright (C) 2012 RedHat inc.
+
+ 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.
+*/
+#include "internal_libreport.h"
+#include "internal_libreport_gtk.h"
+
+#include <gio/gio.h>
+
+#define secrets_api "org.freedesktop.secrets"
+#define SECRETS_NAME_SPACE(interface) "org.freedesktop.Secret."interface
+
+/* label and alias have to be same because of ksecrets */
+#define SECRETS_COLLECTION_LABEL SECRETS_COLLECTION_ALIAS
+#define SECRETS_COLLECTION_ALIAS "default"
+
+#define SECRETS_SEARCH_ATTRIBUTE "libreportEventConfig"
+
+/* 5s timeout*/
+#define SECRETS_CALL_DEFAULT_TIMEOUT 5000
+
+/* Well known errors for which we have workarounds */
+#define NOT_IMPLEMENTED_READ_ALLIAS_ERROR_MESSAGE
"GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method
'ReadAlias' in interface 'org.freedesktop.Secret.Service' at object path
'/org/freedesktop/secrets' (signature 's')"
+#define INVALID_PROPERTIES_ARGUMENTS
"GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Invalid properties
argument"
+
+
+enum secrets_api_state
+{
+ /* intial state */
+ SBS_INITIAL = 0,
+ /* after opening a secrets service session (different than D-Bus session) */
+ SBS_CONNECTED,
+ /* secrets service is not available do not bother user anymore */
+ SBS_UNAVAILABLE,
+};
+
+struct secrets_object
+{
+ GDBusProxy *proxy;
+ /*
http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfac... */
+ GDBusProxy *property_proxy;
+ const gchar *interface_name;
+};
+
+/* Glib DBus object org.freedesktop.secrets */
+GDBusConnection *g_connection;
+
+/* State of the service */
+enum secrets_api_state g_state;
+
+/*
http://standards.freedesktop.org/secret-service/re01.html */
+/* xorg.freedesktop.Secret.Service interface object */
+struct secrets_object *g_service;
+
+/*
http://standards.freedesktop.org/secret-service/ch06.html */
+/* xorg.freedesktop.Secret.Session interface object */
+GDBusProxy *g_session;
+
+/*******************************************************************************/
+/* struct secrets_object */
+/*******************************************************************************/
+
+static struct secrets_object *secrets_object_new_from_proxy(GDBusProxy *proxy)
+{
+ struct secrets_object *obj = xmalloc(sizeof(*obj));
+ obj->proxy = proxy;
+ obj->property_proxy = NULL;
+ obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
+
+ return obj;
+}
+
+static struct secrets_object *secrets_object_new(const gchar *path,
+ const gchar *interface,
+ GError **error)
+{
+ GDBusProxy *const proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api, path, interface, NULL, error);
+
+ if (*error)
+ return NULL;
+
+ return secrets_object_new_from_proxy(proxy);
+}
+
+static void secrets_object_change_path(struct secrets_object *obj,
+ const gchar *path,
+ GError **error)
+{
+ GDBusProxy *const proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api, path, obj->interface_name, NULL, error);
+
+ if (!*error)
+ {
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ obj->proxy = proxy;
+ }
+}
+
+static void secrets_object_delete(struct secrets_object *obj)
+{
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ if (obj->property_proxy)
+ g_object_unref(obj->property_proxy);
+
+ free(obj);
+}
+
+static GDBusProxy *secrets_object_get_properties_proxy(struct secrets_object *obj, GError
**error)
+{
+ if (!obj->property_proxy)
+ {
+ obj->property_proxy
+ = g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api,
g_dbus_proxy_get_object_path(obj->proxy),
+ "org.freedesktop.DBus.Properties", NULL,
error);
+
+ if (*error)
+ return NULL;
+ }
+
+ return obj->property_proxy;
+}
+
+
+static void secrets_object_set_dbus_property(struct secrets_object *obj,
+ const char *property,
+ GVariant *value,
+ GError **error)
+{
+ GDBusProxy *properties = secrets_object_get_properties_proxy(obj, error);
+
+ if (!*error)
+ {
+ GVariant *const ret
+ = g_dbus_proxy_call_sync(properties, "Set",
+ g_variant_new("(ssv)",
obj->interface_name, property, value),
+ G_DBUS_PROXY_FLAGS_NONE,
SECRETS_CALL_DEFAULT_TIMEOUT, NULL,
+ error);
+
+ if (ret)
+ g_variant_unref(ret);
+ }
+}
+
+/*******************************************************************************/
+/* struct secrets_api */
+/*******************************************************************************/
+
+static void secrets_api_set_unavailable();
+
+static void secrets_api_error(GError* error, const char *comment)
+{
+ if (comment)
+ {
+ log("%s : %s\n", comment, error->message);
+ }
+ else
+ {
+ log("%s\n", error->message);
+ }
+
+ g_error_free(error);
+ secrets_api_set_unavailable();
+}
+
+/*
+ * Initialize all global variables
+ */
+static enum secrets_api_state secrets_api_connect()
+{
+ GError *error = NULL;
+
+ g_connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (g_connection == NULL)
+ {
+ secrets_api_error(error, "failed to open connection to D-Bus
session");
+ return SBS_UNAVAILABLE;
+ }
+
+ g_service = secrets_object_new("/org/freedesktop/secrets",
SECRETS_NAME_SPACE("Service"), &error);
+ if (error)
+ {
+ secrets_api_error(error, "failed to create service");
+ return SBS_UNAVAILABLE;
+ }
+
+ GVariant *const alg_in = g_variant_new_string("");
+ GVariant *const args = g_variant_new("(sv)", "plain", alg_in);
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "OpenSession", args,
G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, &error);
+
+ g_variant_unref(alg_in);
+
+ if (error)
+ {
+ secrets_api_error(error, "cannot open session Secrets session");
+ return SBS_UNAVAILABLE;
+ }
+
+ GVariant *const var = g_variant_get_child_value(ret, 1);
+ g_variant_unref(ret);
+
+ const gchar *const session_path = g_variant_get_string(var, NULL);
+ g_session = g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
secrets_api,
+ session_path,
SECRETS_NAME_SPACE("Session"), NULL, &error);
+
+ g_variant_unref(var);
+
+ if (error)
+ {
+ secrets_api_error(error, "cannot create Proxy for Secrets session");
+ return SBS_UNAVAILABLE;
+ }
+
+ g_state = SBS_CONNECTED;
+
+ return g_state;
+}
+
+static void secrets_api_init()
+{
+ if (g_state == SBS_INITIAL)
+ secrets_api_connect();
+}
+
+static void secrets_api_close()
+{
+ if (g_state == SBS_INITIAL || g_state == SBS_UNAVAILABLE)
+ return;
+
+ GError *error = NULL;
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_session, "Close",
g_variant_new("()"), G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, &error);
+
+ if (error)
+ {
+ VERB1 log("error on closing Secrets session : %s\n",
error->message);
+ g_error_free(error);
+ }
+ else
+ g_variant_unref(ret);
+
+ g_object_unref(g_session);
+
+ secrets_object_delete(g_service);
+
+ g_object_unref(g_connection);
+
+ g_connection = NULL;
+ g_service = NULL;
+ g_session = NULL;
+ g_state = SBS_INITIAL;
+}
+
+static void secrets_api_set_unavailable()
+{
+ secrets_api_close();
+
+ g_state = SBS_UNAVAILABLE;
+}
+
+static int is_secrets_api_available()
+{
+ return g_state != SBS_UNAVAILABLE;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Prompt */
+/*
http://standards.freedesktop.org/secret-service/re05.html */
+/*******************************************************************************/
+
+struct prompt_source
+{
+ GSource source;
+ GDBusProxy *prompt_proxy;
+ GError *error;
+ GMainLoop *loop;
+};
+
+struct prompt_call_back_args
+{
+ gboolean dismissed;
+ GVariant *result;
+ GMainLoop *loop;
+};
+
+static void prompt_quit_loop(GMainLoop* loop)
+{
+ if (g_main_loop_is_running(loop))
+ g_main_loop_quit(loop);
+}
+
+static gboolean prompt_g_idle_prepare(GSource *source_data, gint *timeout)
+{
+ return TRUE;
+}
+
+static gboolean prompt_g_idle_check(GSource *source)
+{
+ return TRUE;
+}
+
+static gboolean prompt_g_idle_dispatch(GSource *source, GSourceFunc callback, gpointer
user_data)
+{
+ struct prompt_source *const prompt_source = (struct prompt_source *)source;
+ GDBusProxy *const prompt_proxy = prompt_source->prompt_proxy;
+ GVariant *const args = g_variant_new("(s)","");
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(prompt_proxy, "Prompt", args,
G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL,
&prompt_source->error);
+
+ if (prompt_source->error)
+ prompt_quit_loop(prompt_source->loop);
+ else
+ g_variant_unref(ret);
+
+ /* always return FALSE, it means that this dispatch is to be removed from idle loop
*/
+ return FALSE;
+}
+
+static void prompt_call_back(GDBusConnection *connection, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *parameters,
+ gpointer user_data)
+{
+ struct prompt_call_back_args *const args = (struct prompt_call_back_args
*)user_data;
+ GVariant *const result = NULL;
+
+ g_variant_get(parameters, "(bv)", &args->dismissed, &result);
+
+ if (!args->dismissed)
+ args->result = result;
+ else
+ g_variant_unref(result);
+
+ prompt_quit_loop(args->loop);
+}
+
+static bool is_prompt_required(const char *prompt_path)
+{
+ return prompt_path && !(prompt_path[0] == '/' &&
prompt_path[1] == '\0');
+}
+
+static bool secrets_prompt(const char *prompt_path,
+ GVariant **result,
+ GError **error)
+{
+ static GSourceFuncs idle_funcs = {
+ .prepare = prompt_g_idle_prepare,
+ .check = prompt_g_idle_check,
+ .dispatch = prompt_g_idle_dispatch,
+ .finalize = NULL,
+ .closure_callback = NULL,
+ .closure_marshal = NULL,
+ };
+
+ GDBusProxy *const prompt_proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL, secrets_api,
+ prompt_path, SECRETS_NAME_SPACE("Prompt"), NULL,
error);
+
+ if (*error)
+ return true;
+
+ GMainContext *context = g_main_context_get_thread_default();
+ GMainLoop *loop = g_main_loop_new(context, FALSE);
+
+ struct prompt_call_back_args args = {
+ .result=NULL,
+ .dismissed=FALSE,
+ .loop=loop
+ };
+
+ const guint signal_ret =
+ g_dbus_connection_signal_subscribe(g_connection, secrets_api,
SECRETS_NAME_SPACE("Prompt"),
+ "Completed", prompt_path, NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
+ prompt_call_back, &args, NULL);
+
+ /* prepare prompt source */
+ struct prompt_source *const prompt_source =
+ (struct prompt_source*)g_source_new(&idle_funcs, sizeof(*prompt_source));
+
+ prompt_source->prompt_proxy = prompt_proxy;
+ prompt_source->error = NULL;
+
+ g_source_attach((GSource*)prompt_source, context);
+
+ g_main_loop_run(loop);
+
+ /* save error */
+ *error = prompt_source->error;
+ if (args.result)
+ {
+ if (result)
+ *result = args.result;
+ else
+ g_variant_unref(args.result);
+ }
+
+ /* destroy prompt source */
+ g_object_unref(prompt_proxy);
+ g_source_destroy((GSource*)prompt_source);
+
+ g_dbus_connection_signal_unsubscribe(g_connection, signal_ret);
+ g_main_loop_unref(loop);
+
+ return args.dismissed;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Service */
+/*
http://standards.freedesktop.org/secret-service/re01.html */
+/*******************************************************************************/
+
+static bool secrets_service_unlock(const gchar *const *objects,
+ GVariant **unlocked,
+ GError **error)
+{
+ bool dismissed = false;
+ GVariant *const args = g_variant_new("(^ao)", objects);
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "Unlock", args,
G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ if (*error)
+ return dismissed;
+
+ gchar *prompt;
+ GVariant *result;
+ g_variant_get(ret, "(@aoo)", &result, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(result);
+ result = NULL;
+ dismissed = secrets_prompt(prompt, &result, error);
+ }
+
+ if (result)
+ { /* in case of error or dismissed the result variable has NULL value */
+ if (unlocked)
+ *unlocked = result;
+ else
+ g_variant_unref(result);
+ }
+
+ g_variant_unref(ret);
+
+ return dismissed;
+}
+
+static bool secrets_service_unlock_object(struct secrets_object *obj,
+ GError **error)
+{
+ const gchar *const locked[] = {
+ g_dbus_proxy_get_object_path(obj->proxy),
+ NULL
+ };
+
+ GVariant *result = NULL;
+ bool dismissed = secrets_service_unlock(locked, &result, error);
+
+ if (!*error && !dismissed && result)
+ {
+ gsize length;
+ const gchar *const *unlocked = g_variant_get_objv(result, &length);
+
+ /* ksecrets doesn't set dismissed correctly */
+ if (length)
+ {
+ secrets_object_change_path(obj, *unlocked, error);
+ g_free((gpointer)unlocked);
+ }
+ else
+ dismissed = true;
+
+ g_variant_unref(result);
+ }
+
+ return dismissed;
+}
+
+/*
+ * KSecretsService doesn't implement ReadAlias
+ */
+static struct secrets_object *secrets_service_find_collection(const char *alias, GError
**error)
+{
+ /* do not unref this proxy, the proxy is owned by secrets object */
+ GDBusProxy *const properties = secrets_object_get_properties_proxy(g_service,
error);
+
+ if (*error)
+ return NULL;
+
+ /* cannot use g_dbus_proxy_get_cached_property() because of */
+ /* errors in case when secrets service was restarted during session */
+ GVariant *const prop =
+ g_dbus_proxy_call_sync(properties, "Get",
+ g_variant_new("(ss)",
SECRETS_NAME_SPACE("Service"), "Collections"),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
NULL, error);
+
+ if (*error)
+ return NULL;
+
+ struct secrets_object *collection = NULL;
+
+ /* Returned value */
+ GVariant *const value = g_variant_get_child_value(prop,0);
+ /* Contentet of returned value */
+ GVariant *const list = g_variant_get_child_value(value,0);
+
+ GVariantIter *iter;
+ char *path;
+ g_variant_get(g_variant_get_child_value(value,0), "ao", &iter);
+
+ while (g_variant_iter_loop(iter, "o", &path))
+ {
+ GDBusProxy *const collection_proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
secrets_api,
+ path, SECRETS_NAME_SPACE("Collection"), NULL,
error);
+
+ if (*error)
+ break;
+
+ GVariant *const lbl_var = g_dbus_proxy_get_cached_property(collection_proxy,
+ "Label");
+
+ const gchar *const label = g_variant_get_string(lbl_var, NULL);
+ const int found = label && (strcmp(label, alias) == 0);
+
+ /* found is introduced because of break if collecion is found */
+ /* and I want to write the following statement just once */
+ /* the label variable is invalid after the following statement */
+ g_variant_unref(lbl_var);
+
+ if (found)
+ {
+ collection = secrets_object_new_from_proxy(collection_proxy);
+ break;
+ }
+
+ g_object_unref(collection_proxy);
+ }
+
+ g_variant_iter_free(iter);
+ g_variant_unref(list);
+ g_variant_unref(value);
+ g_variant_unref(prop);
+
+ return collection;
+}
+
+static struct secrets_object *secrets_service_read_allias(const char *alias, GError
**error)
+{
+ struct secrets_object *collection = NULL;
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "ReadAlias",
g_variant_new("(s)", alias),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, error);
+
+ if (!*error)
+ {
+ GVariant *const obj_path = g_variant_get_child_value(ret, 0);
+ const gchar* const coll_path = g_variant_get_string(obj_path, NULL);
+
+ if (strcmp(coll_path,"/"))
+ collection = secrets_object_new(coll_path,
+
SECRETS_NAME_SPACE("Collection"),
+ error);
+
+ g_variant_unref(obj_path);
+ g_variant_unref(ret);
+ }
+ else if (!strcmp((*error)->message, NOT_IMPLEMENTED_READ_ALLIAS_ERROR_MESSAGE))
+ {
+ /* ksecrets doesn't implement ReadAlias, thus search by label is performed
*/
+ VERB1 log("ReadAlias failed, going to search on the client side : %s",
(*error)->message);
+ g_error_free(*error);
+ *error = NULL;
+ collection = secrets_service_find_collection(alias, error);
+ }
+
+ return collection;
+}
+
+static struct secrets_object *secrets_service_create_collection(const gchar *label,
+ const gchar *alias,
+ bool *dismissed,
+ GError **error)
+{
+ GVariantBuilder *const prop_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}",
"org.freedesktop.Secret.Collection.Label",
+ g_variant_new_string(label) );
+
+ GVariant *const call_args =
+ g_variant_new("(@a{sv}s)", g_variant_builder_end(prop_builder),
alias);
+
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "CreateCollection",
call_args, G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ *dismissed = false;
+
+ if (*error)
+ return NULL;
+
+ gchar *prompt;
+ GVariant *collection_var;
+ g_variant_get(ret, "(@oo)", &collection_var, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(collection_var);
+ collection_var = NULL;
+
+ *dismissed = secrets_prompt(prompt, &collection_var, error);
+ }
+
+ struct secrets_object *collection = NULL;
+ if (collection_var)
+ {
+ gsize length;
+ const gchar *const coll_path = g_variant_get_string(collection_var,
&length);
+
+ /* ksecrets doens't properly set dismissed */
+ /* if dismissmised is false and result is empty */
+ /* then ksecrets didn't properly set dismissed */
+ if (length)
+ collection = secrets_object_new(coll_path,
SECRETS_NAME_SPACE("Collection"), error);
+ else
+ *dismissed = true;
+
+ g_variant_unref(collection_var);
+ }
+
+ g_variant_unref(ret);
+
+ return collection;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Collection */
+/*
http://standards.freedesktop.org/secret-service/re02.html */
+/*******************************************************************************/
+
+static GVariant *create_item_call_args(const char *event_name,
+ GVariant *attributes,
+ GDBusProxy *session,
+ bool full_property_name)
+{
+ /* ksecrets accepts properties without namespace */
+ /* gnome-keyring accepts only with namespace */
+ const char *const lbl_name = full_property_name ?
SECRETS_NAME_SPACE("Item.Label")
+ : "Label";
+ const char *const att_name = full_property_name ?
SECRETS_NAME_SPACE("Item.Attributes")
+ : "Attributes";
+
+ GVariantBuilder *const prop_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}", lbl_name,
g_variant_new_string(event_name));
+ g_variant_builder_add(prop_builder, "{sv}", att_name, attributes);
+
+ GVariantBuilder *const param_builder =
g_variant_builder_new(G_VARIANT_TYPE("ay"));
+ GVariantBuilder *const value_builder =
g_variant_builder_new(G_VARIANT_TYPE("ay"));
+
+ GVariant *const secret = g_variant_new("(o@ay@ays)",
+ g_dbus_proxy_get_object_path(session),
+ g_variant_builder_end(param_builder),
+ g_variant_builder_end(value_builder),
+ "text/plain, charset=utf8");
+
+ return g_variant_new("(@a{sv}@(oayays)b)",
g_variant_builder_end(prop_builder), secret, FALSE);
+}
+
+static void secrets_collection_create_text_item(struct secrets_object *collection,
+ const char *event_name,
+ GVariant *attributes,
+ GError **error)
+{
+ GVariant *call_args, *ret;
+
+ /* ksecrets silently drops properties with full names */
+ /* gnome-keyring returns an error if a property key is not full property name */
+ /* the first iteration sends property names without namespace */
+ /* if NO error was returned service is ksecrets and everything is ok */
+ /* if error was returned service is gnome-keyring and and the second iteration */
+ /* must be performed */
+ /* the second iteration sends property names with namespace */
+ static bool const options[] = {FALSE, TRUE};
+ for (size_t choice = 0; choice < sizeof(options)/sizeof(*options); ++choice)
+ {
+ if (*error)
+ { /* this code is here because I want to return an error */
+ /* it cannot be on the end of the loop because I need unfreed error after the
last iteration */
+ g_error_free(*error);
+ *error = NULL;
+ }
+
+ call_args = create_item_call_args(event_name, g_variant_ref(attributes),
+ g_session, options[choice]);
+
+ ret = g_dbus_proxy_call_sync(collection->proxy, "CreateItem",
call_args,
+ G_DBUS_PROXY_FLAGS_NONE,
SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, error);
+
+ if (*error)
+ {
+ if (!strcmp(INVALID_PROPERTIES_ARGUMENTS, (*error)->message))
+ {
+ VERB2 log("CreateItem failed, going to use other properties names :
%s", (*error)->message);
+ continue;
+ }
+ else
+ /* if the error wasn't about invalid properties we have an another
problem */
+ break;
+ }
+
+ gchar *prompt, *item;
+ g_variant_get(ret, "(oo)", &item, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ const bool dismissed = secrets_prompt(prompt, NULL, error);
+ if (*error || dismissed)
+ secrets_api_set_unavailable();
+
+ g_variant_unref(ret);
+ break;
+ }
+
+ g_variant_unref(ret);
+ }
+
+ g_variant_unref(attributes);
+}
+
+/*
+ * Performs org.freedesktop.Secret.Collection.SearchItems and returns the first
+ * item from result. A found item can be possibly locked.
+ */
+static struct secrets_object *secrets_collection_search_one_item(
+ const struct secrets_object
*collection,
+ GVariant *search_attrs,
+ GError **error)
+{
+ struct secrets_object *item = NULL;
+ GVariant *const value =
+ g_dbus_proxy_call_sync(collection->proxy, "SearchItems",
g_variant_new("(@a{ss})", search_attrs),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
NULL, error);
+
+ if (*error)
+ return NULL;
+
+ /* gnome-keyring returns (unlocked,locked) */
+ /* ksecretsservice returns (unlocked) */
+ /* all childs has to be taken into account */
+ const gsize n_results = g_variant_n_children(value);
+ for(gsize child = 0; !item && child < n_results; ++child)
+ {
+ if (*error)
+ {
+ VERB1 log("search items result error : %s", (*error)->message);
+ free(*error);
+ *error = NULL;
+ }
+
+ GVariant *const paths = g_variant_get_child_value(value, 0);
+ const gchar *const *item_paths = g_variant_get_objv(paths, NULL);
+
+ /* the first found object path is to be returned */
+ if (*item_paths)
+ item = secrets_object_new(*item_paths, SECRETS_NAME_SPACE("Item"),
error);
+
+ g_free((gpointer)item_paths);
+ g_variant_unref(paths);
+ }
+
+ g_variant_unref(value);
+
+ return item;
+}
+
+/*******************************************************************************/
+/* utility functions */
+/*******************************************************************************/
+
+static struct secrets_object *get_default_collection(GError **error)
+{
+ bool dismissed = false;
+ struct secrets_object *collection =
+ secrets_service_read_allias(SECRETS_COLLECTION_ALIAS, error);
+
+ if (!*error && collection)
+ {
+ dismissed = secrets_service_unlock_object(collection, error);
+
+ if (*error || dismissed)
+ {
+ secrets_object_delete(collection);
+ collection = NULL;
+ }
+ }
+
+ if (dismissed)
+ /* A user canceled an unlock prompt */
+ /* Secrets BUS is set to unvailable in order to not bother a user */
+ /* with an another unlock prompt */
+ secrets_api_set_unavailable();
+
+ /* no matter if error occured or prompt was dismissed */
+ /* in these cases the collection holds NULL */
+ return collection;
+}
+
+static void load_event_options_from_item(GList *options,
+ struct secrets_object *item)
+{
+ GVariant *const attributes =
+ g_dbus_proxy_get_cached_property(item->proxy, "Attributes");
+
+ GVariantIter *iter;
+ g_variant_get(attributes, "a{ss}", &iter);
+
+ char *name, *value;
+ while (g_variant_iter_loop(iter, "{ss}", &name, &value))
+ {
+ event_option_t *const option = get_event_option_from_list(name, options);
+ if (option)
+ {
+ free(option->eo_value);
+ option->eo_value = xstrdup(value);
+ VERB2 log("loaded event option : '%s => %s'", name,
option->eo_value);
+ }
+ }
+
+ g_variant_unref(attributes);
+}
+
+static GVariant *create_attributes_from_options(GList *options,
+ const char *event_name,
+ bool store_passwords)
+{
+ GVariantBuilder *const attr_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ for (GList *iter = g_list_first(options); iter; iter = g_list_next(iter))
+ { /* we store data in the search attributes */
+ event_option_t *const op = (event_option_t *)iter->data;
+ if (op->eo_type != OPTION_TYPE_PASSWORD || store_passwords)
+ g_variant_builder_add(attr_builder, "{ss}", op->eo_name,
op->eo_value);
+ }
+
+ /* and finaly, add extra attribute used for searching */
+ g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE,
event_name);
+
+ return g_variant_builder_end(attr_builder);
+}
+
+static struct secrets_object *find_item_by_event_name(const struct secrets_object
*collection,
+ const char* event_name,
+ GError **error)
+{
+ GVariantBuilder *const attrs = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(attrs, "{ss}", SECRETS_SEARCH_ATTRIBUTE,
event_name);
+
+ return secrets_collection_search_one_item(collection, g_variant_builder_end(attrs),
error);
+}
+
+static void load_settings(struct secrets_object *collection, const char *event_name,
event_config_t *ec)
+{
+ GError *error = NULL;
+ GVariant *const locked = g_dbus_proxy_get_cached_property(collection->proxy,
"Locked");
+
+ if (g_variant_get_boolean(locked))
+ {
+ /* a collection can be closed by the service for some reason */
+ /* set api unavailable if unlock attempt was dismissed */
+ if (secrets_service_unlock_object(collection, &error))
+ secrets_api_set_unavailable();
+ }
+
+ g_variant_unref(locked);
+
+ if (!error)
+ {
+ struct secrets_object *item = find_item_by_event_name(collection, event_name,
&error);
+
+ if (!error && item)
+ {
+ VERB2 log("loading event config : '%s'", event_name);
+ load_event_options_from_item(ec->options, item);
+ secrets_object_delete(item);
+ }
+ }
+
+ if (error)
+ secrets_api_error(error, NULL);
+}
+
+static void load_event_config(gpointer key, gpointer value, gpointer user_data)
+{
+ char *const event_name = (char*)key;
+ event_config_t *const ec = (event_config_t *)value;
+ struct secrets_object *collection = (struct secrets_object *)user_data;
+
+ if (is_secrets_api_available())
+ load_settings(collection, event_name, ec);
+}
+
+static void save_options(struct secrets_object *collection,
+ const char* event_name,
+ GList *options,
+ bool store_passwords,
+ GError **error)
+{
+ struct secrets_object *item;
+ item = find_item_by_event_name(collection, event_name, error);
+
+ if (*error)
+ return;
+
+ GVariant *const attributes = create_attributes_from_options(options,
+ event_name,
+ store_passwords);
+
+ if (item)
+ { /* item exists */
+ VERB2 log("updating event config : '%s'", event_name);
+ secrets_object_set_dbus_property(item, "Attributes", attributes,
error);
+ secrets_object_delete(item);
+ }
+ else /* create a new item */
+ {
+ VERB2 log("creating event config : '%s'", event_name);
+ secrets_collection_create_text_item(collection, event_name, attributes, error);
+ }
+}
+
+static void save_event_config(const char *event_name,
+ GList *options,
+ bool store_passwords)
+{
+ bool dismissed = false;
+ GError *error = NULL;
+ struct secrets_object *collection = get_default_collection(&error);
+
+ if (error)
+ goto save_event_config_end;
+
+ if (!collection)
+ {
+ VERB2 log("going to create a new default collection
'"SECRETS_COLLECTION_LABEL"'"
+ " with alias
'"SECRETS_COLLECTION_ALIAS"'");
+
+ collection =
+ secrets_service_create_collection(SECRETS_COLLECTION_LABEL,
SECRETS_COLLECTION_ALIAS,
+ &dismissed, &error);
+
+ if (error || dismissed)
+ goto save_event_config_end;
+ }
+
+ save_options(collection, event_name, options, store_passwords, &error);
+
+save_event_config_end:
+ if (dismissed)
+ secrets_api_set_unavailable();
+
+ if (error)
+ secrets_api_error(error, NULL);
+}
+
+/******************************************************************************/
+/* Public interface */
+/******************************************************************************/
+
+bool is_event_config_user_storage_available()
+{
+ return is_secrets_api_available();
+}
+
+/*
+ * Loads event config options for passed events
+ *
+ * @param event_config_list Events configs
+ */
+void load_event_config_data_from_user_storage(GHashTable *event_config_list)
+{
+ secrets_api_init();
+
+ if (is_secrets_api_available())
+ {
+ GError *error = NULL;
+ struct secrets_object *collection = get_default_collection(&error);
+
+ if (collection)
+ {
+ g_hash_table_foreach(event_config_list, &load_event_config, collection);
+ secrets_object_delete(collection);
+ }
+
+ if (error)
+ secrets_api_error(error, "D-Bus secrets API cannot provide default
collection");
+ }
+ else
+ {
+ VERB1 log("cannot load user's configuration due to unavailability of
D-Bus secrets API");
+ }
+}
+
+/*
+ * Saves an event_config options to some kind of session storage.
+ *
+ * @param event_name Lookup key
+ * @param event_config Event data
+ * @param store_passwords If TRUE stores options with passwords, otherwise skips them
+ */
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_passwords)
+{
+ secrets_api_init();
+
+ if (is_secrets_api_available())
+ save_event_config(event_name, event_config->options, store_passwords);
+ else
+ {
+ VERB1 log("cannot save user's configuration due to unavailability of
D-Bus secrets API");
+ }
+}
diff --git a/src/gui-wizard-gtk/main.c b/src/gui-wizard-gtk/main.c
index 05d01e9..e9632c3 100644
--- a/src/gui-wizard-gtk/main.c
+++ b/src/gui-wizard-gtk/main.c
@@ -140,7 +140,7 @@ int main(int argc, char **argv)
/* load /etc/abrt/events/foo.{conf,xml} stuff
and $XDG_CACHE_HOME/abrt/events/foo.conf */
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
load_user_settings("report-gtk");
reload_problem_data_from_dump_dir();
--
1.7.7.6