- all configuration options are stored as binary data in Secret
  member of Item interface
- each option is stored as c-string 'name=value\0' and a secret value
  consists from concatenation of corresponding strings for all options
- lookup attributes contain only 'libreportEventConfig' key and and the
  key is associated with event name

Signed-off-by: Jakub Filak <[email protected]>
---
 src/gtk-helpers/secrets.c | 298 +++++++++++++++++++++++++++++++---------------
 1 file changed, 205 insertions(+), 93 deletions(-)

diff --git a/src/gtk-helpers/secrets.c b/src/gtk-helpers/secrets.c
index ee6a348..c9c32e1 100644
--- a/src/gtk-helpers/secrets.c
+++ b/src/gtk-helpers/secrets.c
@@ -29,6 +29,7 @@
 #define SECRETS_COLLECTION_ALIAS "default"
 
 #define SECRETS_SEARCH_ATTRIBUTE "libreportEventConfig"
+#define SECRETS_OPTION_VALUE_DELIMITER '='
 
 /* 5s timeout*/
 #define SECRETS_CALL_DEFAULT_TIMEOUT 5000
@@ -36,6 +37,7 @@
 /* Well known errors for which we have workarounds */
 #define NOT_IMPLEMENTED_READ_ALIAS_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"
+#define GNOME_KEYRING_NOT_HAVING_SECRET_ERROR_MESSAGE 
"GDBus.Error:org.freedesktop.DBus.Error.Failed: Couldn't get item secret"
 
 /*
    Data structure:
@@ -47,12 +49,23 @@
 
    Collection is a set of Items identified by labels (label is a string).
    Libreport uses event names as labels for items which hold event config data.
+   One item holds all configuration items for one event.
+
+   Item has a set of (name,value) pairs called lookup attributes.
+   Libreport uses 'libreportEventConfig' attribute for searching. The
+   attribute hodls an event name.
+   Item has a secret value. The value holds an arbitrary binary data.
+   Libreport uses the value to store configuration items.
+   The value consists from several c-strings where each option is
+   represented by one c-string (bytes followed by '\0').
+   Each option string is concatenation of option name, delimiter and option 
value.
+
+
+   For example, "report_Bugzilla" item will have the lookup attribute
+   like ("libreportEventConfig", "report_Bugzilla") and the secret value
+   like ("Bugzilla_URL=https://bugzilla.redhat.com\0Bugzilla_Login=foo\0";)
 
-   Item is a set of (name,value) pairs called attributes.
-   Libreport uses attributes to store config data.
 
-   For example, "report_Bugzilla" item will have some attributes,
-   like ("Bugzilla_BugzillaURL", "https://bugzilla.redhat.com";).
 
    DBus API:
    =========
@@ -246,32 +259,6 @@ static GDBusProxy 
*secrets_object_get_properties_proxy(struct secrets_object *ob
     return obj->property_proxy;
 }
 
-
-static bool secrets_object_set_dbus_property(struct secrets_object *obj,
-                                             const char *property,
-                                             GVariant *value)
-{
-    GDBusProxy *properties = secrets_object_get_properties_proxy(obj);
-
-    if (!properties)
-        return false;
-
-    /* 
http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
 */
-    /* org.freedesktop.DBus.Properties.Set (IN String interface_name, IN 
String property_name, IN Variant value); */
-    GVariant *const resp = dbus_call_sync(properties,
-                                          "Set",
-                                          g_variant_new("(ssv)",
-                                                        obj->interface_name,
-                                                        property,
-                                                        value)
-                                         );
-
-    if (resp)
-        g_variant_unref(resp);
-
-    return resp;
-}
-
 
/*******************************************************************************/
 /* struct secrets_service                                                      
  */
 
/*******************************************************************************/
@@ -472,6 +459,7 @@ static GVariant *secrets_prompt(const char *prompt_path,
         .closure_marshal = NULL,
     };
 
+    /* TODO : leak */
     GDBusProxy *const prompt_proxy = get_dbus_proxy(prompt_path, 
SECRETS_NAME_SPACE("Prompt"));
 
     *dismissed = false;
@@ -544,6 +532,7 @@ static GVariant *secrets_service_unlock(const gchar *const 
*objects,
 
     gchar *prompt = NULL;
     GVariant *result = NULL;
+    /* TODO : leak */
     g_variant_get(var, "(@aoo)", &result, &prompt);
 
     *dismissed = false;
@@ -774,6 +763,7 @@ static struct secrets_object 
*secrets_service_create_collection(const gchar *lab
 
     gchar *prompt = NULL;
     GVariant *collection_var = NULL;
+    /* TODO : leak */
     g_variant_get(resp, "(@oo)", &collection_var, &prompt);
 
     if (is_prompt_required(prompt))
@@ -810,7 +800,7 @@ static struct secrets_object 
*secrets_service_create_collection(const gchar *lab
 
 static GVariant *create_item_call_args(const char *event_name,
                                        GVariant *attributes,
-                                       GDBusProxy *session,
+                                       GVariant *secret,
                                        bool full_property_name)
 {
     /* gnome-keyring accepts properties with namespace */
@@ -842,21 +832,60 @@ static GVariant *create_item_call_args(const char 
*event_name,
     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"));
+    GVariant *const prop = g_variant_builder_end(prop_builder);
+
+    return g_variant_new("(@a{sv}@(oayays)b)", prop, secret, FALSE);
+}
+
+static GVariant *create_secret_from_options(GDBusProxy *session, GList 
*options, bool store_passwords)
+{
     GVariantBuilder *const value_builder = 
g_variant_builder_new(G_VARIANT_TYPE("ay"));
+    for (GList *iter = g_list_first(options); iter; iter = g_list_next(iter))
+    {
+        event_option_t *const op = (event_option_t *)iter->data;
+        /* TODO : is it still necessary? Passwords are encrypted now. */
+        if (op->eo_type != OPTION_TYPE_PASSWORD || store_passwords)
+        {
+            const char *byte = op->eo_name;
+            while(byte[0] != '\0')
+                g_variant_builder_add(value_builder, "y", *byte++);
+
+            g_variant_builder_add(value_builder, "y", 
SECRETS_OPTION_VALUE_DELIMITER);
+
+            byte = op->eo_value;
+            while(byte[0] != '\0')
+                g_variant_builder_add(value_builder, "y", *byte++);
 
-    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");
+            g_variant_builder_add(value_builder, "y", '\0');
+        }
+    }
+
+    GVariant *const value = g_variant_builder_end(value_builder);
 
-    return g_variant_new("(@a{sv}@(oayays)b)", 
g_variant_builder_end(prop_builder), secret, FALSE);
+    return g_variant_new("(oay@ays)",
+                         g_dbus_proxy_get_object_path(session),
+                         /* param */ NULL,
+                         value,
+                         "application/libreport");
+}
+
+static GVariant *create_lookup_attributes(const char *event_name)
+{
+    GVariantBuilder *const attr_builder = 
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+    /* add extra attribute used for searching */
+    g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE, 
event_name);
+
+    /* An example of attributes:               */
+    /* {                                       */
+    /*   "libreportEventConfig": "event_name", */
+    /* }                                       */
+    return g_variant_builder_end(attr_builder);
 }
 
 static bool secrets_collection_create_text_item(struct secrets_object 
*collection,
                                                 const char *event_name,
                                                 GVariant *attributes,
+                                                GVariant *secret,
                                                 bool *dismissed)
 {
     bool succ = false;
@@ -903,8 +932,10 @@ static bool secrets_collection_create_text_item(struct 
secrets_object *collectio
         /*   prompt     : A prompt object, or the special value '/' if no 
prompt */
         /*                is necessary. */
         GVariant *const resp = g_dbus_proxy_call_sync(collection->proxy, 
"CreateItem",
-                                      create_item_call_args(event_name, 
g_variant_ref(attributes),
-                                                            g_session_proxy, 
options[choice]),
+                                      create_item_call_args(event_name,
+                                                            
g_variant_ref(attributes),
+                                                            
g_variant_ref(secret),
+                                                            options[choice]),
                                       G_DBUS_PROXY_FLAGS_NONE, 
SECRETS_CALL_DEFAULT_TIMEOUT,
                                       NULL, &error);
 
@@ -924,6 +955,7 @@ static bool secrets_collection_create_text_item(struct 
secrets_object *collectio
 
         gchar *prompt = NULL;
         gchar *item = NULL;
+        /* TODO : leak */
         g_variant_get(resp, "(oo)", &item, &prompt);
 
         /* if prompt is no required the function is successfull */
@@ -941,6 +973,7 @@ static bool secrets_collection_create_text_item(struct 
secrets_object *collectio
     }
 
     g_variant_unref(attributes);
+    g_variant_unref(secret);
 
     return succ;
 }
@@ -1052,68 +1085,138 @@ static bool get_default_collection(struct 
secrets_object **collection,
     return succ;
 }
 
-static void load_event_options_from_item(GList *options,
+static void load_event_options_from_item(GDBusProxy *session,
+                                         GList *options,
                                          struct secrets_object *item)
 {
-    GVariant *const attributes =
-        g_dbus_proxy_get_cached_property(item->proxy, "Attributes");
+    {   /* for backward compatibility */
+        /* load configuration from lookup attributes but don't store 
configuration in lookup attributes */
+        GVariant *const attributes =
+            g_dbus_proxy_get_cached_property(item->proxy, "Attributes");
 
-    GVariantIter *iter = NULL;
-    g_variant_get(attributes, "a{ss}", &iter);
+        GVariantIter *iter = NULL;
+        g_variant_get(attributes, "a{ss}", &iter);
 
-    gchar *name = NULL;
-    gchar *value = NULL;
-    while (g_variant_iter_loop(iter, "{ss}", &name, &value))
-    {
-        event_option_t *const option = get_event_option_from_list(name, 
options);
-        if (option)
+        gchar *name = NULL;
+        gchar *value = NULL;
+        while (g_variant_iter_loop(iter, "{ss}", &name, &value))
         {
-            free(option->eo_value);
-            option->eo_value = xstrdup(value);
-            VERB2 log("loaded event option : '%s => %s'", name, 
option->eo_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);
     }
 
-    g_variant_unref(attributes);
-}
+    {   /* read configuration from the secret value */
+        GError *error = NULL;
+        GVariant *const resp = g_dbus_proxy_call_sync(item->proxy,
+                                                      "GetSecret",
+                                                      g_variant_new("(o)",
+                                                        
g_dbus_proxy_get_object_path(session)),
+                                                      G_DBUS_PROXY_FLAGS_NONE,
+                                                      
SECRETS_CALL_DEFAULT_TIMEOUT,
+                                                      /* GCancellable */ NULL,
+                                                      &error);
 
-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);
-    }
+        if (error)
+        {   /* if the error is NOT the known error produced by the 
gnome-keyring */
+            /* when no secret value is assigned to an item */
+            /* then let you user known that the error occured */
+            if(strcmp(error->message, 
GNOME_KEYRING_NOT_HAVING_SECRET_ERROR_MESSAGE) != 0)
+                error_msg(_("can't get secret value: %s"), error->message);
+            else
+            {
+                VERB1 log("can't get secret value: %s", error->message);
+            }
 
-    /* and finally, add extra attribute used for searching */
-    g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE, 
event_name);
+            g_error_free(error);
+            return;
+        }
 
-    /* An example of attributes:               */
-    /* {                                       */
-    /*   "BugzillaURL": "Value1",              */
-    /*   "BugzillaPassword": "Value2",         */
-    /*   "libreportEventConfig": "event_name", */
-    /* }                                       */
-    return g_variant_builder_end(attr_builder);
+        if (g_variant_n_children(resp) == 0)
+        {
+            VERB1 log("response from GetSecret() method doesn't contain data: 
'%s'", g_variant_print(resp, TRUE));
+            return;
+        }
+
+        /* All dbus responses are returned inside of an variant */
+        GVariant *const secret = g_variant_get_child_value(resp, 0);
+
+        /* DBus represents a structure as a tuple of values */
+        /* struct Secret
+         * {
+         *   (o)  object path
+         *   (ay) byte array params
+         *   (ay) byte array value
+         *   (s)  string content type
+         * }
+         */
+        if (g_variant_n_children(secret) != 4)
+        {
+            VERB1 log("secret value has invalid structure: '%s'", 
g_variant_print(secret, TRUE));
+            return;
+        }
+
+        /* get (ay) byte array value */
+        GVariant *const secret_value = g_variant_get_child_value(secret, 2);
+
+        gsize nelems;
+        gconstpointer data = g_variant_get_fixed_array(secret_value, &nelems, 
sizeof(guchar));
+
+        while(nelems > 0)
+        {
+            const size_t sz = strlen(data) + 1;
+            if (sz > nelems)
+            {   /* check next size, if is too big then trailing 0 is probably 
missing */
+                VERB1 log("broken secret value, atttempts to read %zdB but 
only %zdB are available)", sz, nelems);
+                break;
+            }
+            nelems -= sz;
+
+            char *name = xstrdup(data);
+            char *value = strchr(name, SECRETS_OPTION_VALUE_DELIMITER);
+            if (!value)
+            {
+                VERB1 log("secret value has invalid format: missing delimiter 
after option name");
+                goto next_option;
+            }
+
+            *value++ = '\0';
+
+            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);
+            }
+
+next_option:
+            free(name);
+            data += sz;
+        }
+
+        g_variant_unref(secret_value);
+        g_variant_unref(secret);
+        g_variant_unref(resp);
+    }
 }
 
 static bool find_item_by_event_name(const struct secrets_object *collection,
                                     const char *event_name,
                                     struct secrets_object **item)
 {
-    GVariantBuilder *const attrs = 
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
-
-    /* For searching uses item's attribute holding an event name */
-    g_variant_builder_add(attrs, "{ss}", SECRETS_SEARCH_ATTRIBUTE, event_name);
-
-    return secrets_collection_search_one_item(collection, 
g_variant_builder_end(attrs), item);
+    return secrets_collection_search_one_item(collection, 
create_lookup_attributes(event_name), item);
 }
 
-static void load_settings(struct secrets_object *collection, const char 
*event_name, event_config_t *ec)
+static void load_settings(GDBusProxy *session, struct secrets_object 
*collection,
+                          const char *event_name, event_config_t *ec)
 {
     /* Locked property is not supported by ksecrets */
     /* thus we have to perform Unlock for each call of load settings */
@@ -1128,7 +1231,7 @@ static void load_settings(struct secrets_object 
*collection, const char *event_n
         if (succ && item)
         {
             VERB2 log("loading event config : '%s'", event_name);
-            load_event_options_from_item(ec->options, item);
+            load_event_options_from_item(session, ec->options, item);
             secrets_object_delete(item);
             item = NULL;
         }
@@ -1145,7 +1248,7 @@ static void load_event_config(gpointer key, gpointer 
value, gpointer user_data)
     struct secrets_object *const collection = (struct secrets_object 
*)user_data;
 
     if (is_event_config_user_storage_available())
-        load_settings(collection, event_name, ec);
+        load_settings(g_session_proxy, collection, event_name, ec);
 }
 
 static bool save_options(struct secrets_object *collection,
@@ -1160,22 +1263,31 @@ static bool save_options(struct secrets_object 
*collection,
     if (!succ)
         return succ;
 
-    GVariant *const attributes = create_attributes_from_options(options,
-                                                                event_name,
-                                                                
store_passwords);
+    GVariant *const secret = create_secret_from_options(g_session_proxy, 
options, store_passwords);
 
     if (item)
-    {   /* item exists, change Attributes */
+    {   /* item exists, change a secret */
         VERB2 log("updating event config : '%s'", event_name);
+        /* can't be dismissed */
         *dismissed = false;
-        succ = secrets_object_set_dbus_property(item, "Attributes", 
attributes);
+        GVariant *const args = g_variant_new("(@(oayays))", secret);
+        GVariant *const resp = dbus_call_sync(item->proxy, "SetSecret", args);
+        succ = resp;
+
+        if (succ)
+            g_variant_unref(resp);
+
         secrets_object_delete(item);
-        item = NULL;
     }
     else
-    {   /* create a new item with Attributes */
+    {   /* create a new item */
+        GVariant *const attributes = create_lookup_attributes(event_name);
         VERB2 log("creating event config : '%s'", event_name);
-        succ = secrets_collection_create_text_item(collection, event_name, 
attributes, dismissed);
+        succ = secrets_collection_create_text_item(collection, event_name, 
attributes, secret, dismissed);
+
+        /* I can't figure out why I have to do the following */
+        g_variant_unref(attributes);
+        g_variant_unref(secret);
     }
 
     return succ;
-- 
1.7.11.4

Reply via email to