From 7268a4111e8242c0d2fc8e5ad8b33190a468ca30 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 30 Nov 2021 11:48:37 +0100 Subject: secret-file-collection: Check for file changes and reload when needed When some other process changes the underlying file, the collection should reload its in-memory content, to reflect the changes. Closes https://gitlab.gnome.org/GNOME/libsecret/-/issues/62 --- libsecret/secret-file-collection.c | 228 +++++++++++++++++++++++++------------ 1 file changed, 155 insertions(+), 73 deletions(-) diff --git a/libsecret/secret-file-collection.c b/libsecret/secret-file-collection.c index 62847b9..1819e83 100644 --- a/libsecret/secret-file-collection.c +++ b/libsecret/secret-file-collection.c @@ -54,6 +54,7 @@ struct _SecretFileCollection guint64 usage_count; GBytes *key; GVariant *items; + guint64 file_last_modified; }; static void secret_file_collection_async_initable_iface (GAsyncInitableIface *iface); @@ -68,6 +69,22 @@ enum { PROP_PASSWORD }; +static guint64 +get_file_last_modified (SecretFileCollection *self) +{ + GFileInfo *info; + guint64 res; + + info = g_file_query_info (self->file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (info == NULL) + return 0; + + res = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + g_object_unref (info); + + return res; +} + static gboolean do_derive_key (SecretFileCollection *self) { @@ -296,17 +313,13 @@ secret_file_collection_class_init (SecretFileCollectionClass *klass) egg_libgcrypt_initialize (); } -static void -on_load_contents (GObject *source_object, - GAsyncResult *result, - gpointer user_data) +static gboolean +load_contents (SecretFileCollection *self, + gchar *contents, /* takes ownership */ + gsize length, + GError **error) { - GFile *file = G_FILE (source_object); - GTask *task = G_TASK (user_data); - SecretFileCollection *self = g_task_get_source_object (task); - gchar *contents; gchar *p; - gsize length; GVariant *variant; GVariant *salt_array; guint32 salt_size; @@ -315,70 +328,25 @@ on_load_contents (GObject *source_object, guint64 usage_count; gconstpointer data; gsize n_data; - GError *error = NULL; - gboolean ret; - - ret = g_file_load_contents_finish (file, result, - &contents, &length, - &self->etag, - &error); - - if (!ret) { - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { - GVariantBuilder builder; - guint8 salt[SALT_SIZE]; - - g_clear_error (&error); - - gcry_create_nonce (salt, sizeof(salt)); - self->salt = g_bytes_new (salt, sizeof(salt)); - self->iteration_count = ITERATION_COUNT; - self->modified = g_date_time_new_now_utc (); - self->usage_count = 0; - - if (!do_derive_key (self)) { - g_task_return_new_error (task, - SECRET_ERROR, - SECRET_ERROR_PROTOCOL, - "couldn't derive key"); - g_object_unref (task); - return; - } - - g_variant_builder_init (&builder, - G_VARIANT_TYPE ("a(a{say}ay)")); - self->items = g_variant_builder_end (&builder); - g_variant_ref_sink (self->items); - g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - - g_task_return_error (task, error); - g_object_unref (task); - return; - } p = contents; if (length < KEYRING_FILE_HEADER_LEN || memcmp (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) { - g_task_return_new_error (task, - SECRET_ERROR, - SECRET_ERROR_INVALID_FILE_FORMAT, - "file header mismatch"); - g_object_unref (task); - return; + g_set_error_literal (error, + SECRET_ERROR, + SECRET_ERROR_INVALID_FILE_FORMAT, + "file header mismatch"); + return FALSE; } p += KEYRING_FILE_HEADER_LEN; length -= KEYRING_FILE_HEADER_LEN; if (length < 2 || *p != MAJOR_VERSION || *(p + 1) != MINOR_VERSION) { - g_task_return_new_error (task, - SECRET_ERROR, - SECRET_ERROR_INVALID_FILE_FORMAT, - "version mismatch"); - g_object_unref (task); - return; + g_set_error_literal (error, + SECRET_ERROR, + SECRET_ERROR_INVALID_FILE_FORMAT, + "version mismatch"); + return FALSE; } p += 2; length -= 2; @@ -407,19 +375,125 @@ on_load_contents (GObject *source_object, g_assert (n_data == salt_size); self->salt = g_bytes_new (data, n_data); + + g_variant_unref (salt_array); + g_variant_unref (variant); + if (!do_derive_key (self)) { - g_task_return_new_error (task, - SECRET_ERROR, - SECRET_ERROR_PROTOCOL, - "couldn't derive key"); - goto out; + g_set_error_literal (error, + SECRET_ERROR, + SECRET_ERROR_PROTOCOL, + "couldn't derive key"); + return FALSE; } - g_task_return_boolean (task, TRUE); + return TRUE; +} + +static gboolean +init_empty_file (SecretFileCollection *self, + GError **error) +{ + GVariantBuilder builder; + guint8 salt[SALT_SIZE]; + + gcry_create_nonce (salt, sizeof(salt)); + self->salt = g_bytes_new (salt, sizeof(salt)); + self->iteration_count = ITERATION_COUNT; + self->modified = g_date_time_new_now_utc (); + self->usage_count = 0; + + if (!do_derive_key (self)) { + g_set_error_literal (error, + SECRET_ERROR, + SECRET_ERROR_PROTOCOL, + "couldn't derive key"); + return FALSE; + } + + g_variant_builder_init (&builder, + G_VARIANT_TYPE ("a(a{say}ay)")); + self->items = g_variant_builder_end (&builder); + g_variant_ref_sink (self->items); + + return TRUE; +} + +static void +ensure_up_to_date (SecretFileCollection *self) +{ + guint64 last_modified; + + last_modified = get_file_last_modified (self); + if (last_modified != self->file_last_modified) { + gchar *contents = NULL; + gsize length = 0; + gboolean success; + GError *error = NULL; + + self->file_last_modified = last_modified; + g_clear_pointer (&self->etag, g_free); + + success = g_file_load_contents (self->file, NULL, &contents, &length, &self->etag, &error); + + if (!success && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_clear_error (&error); + + success = init_empty_file (self, &error); + } + + if (success) + success = load_contents (self, contents, length, &error); + + if (!success) + g_debug ("Failed to load file contents: %s", error ? error->message : "Unknown error"); + + g_clear_error (&error); + } +} + +static void +on_load_contents (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (source_object); + GTask *task = G_TASK (user_data); + SecretFileCollection *self = g_task_get_source_object (task); + gchar *contents; + gsize length; + GError *error = NULL; + gboolean ret; + + self->file_last_modified = get_file_last_modified (self); + + ret = g_file_load_contents_finish (file, result, + &contents, &length, + &self->etag, + &error); + + if (!ret) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_clear_error (&error); + + if (init_empty_file (self, &error)) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + } + + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ret = load_contents (self, contents, length, &error); + if (ret) + g_task_return_boolean (task, ret); + else + g_task_return_error (task, error); - out: - g_variant_unref (salt_array); - g_variant_unref (variant); g_object_unref (task); } @@ -546,6 +620,8 @@ secret_file_collection_replace (SecretFileCollection *self, GDateTime *created = NULL; GDateTime *modified; + ensure_up_to_date (self); + hashed_attributes = hash_attributes (self, attributes); if (!hashed_attributes) { g_set_error (error, @@ -657,6 +733,8 @@ secret_file_collection_search (SecretFileCollection *self, GVariant *child; GList *result = NULL; + ensure_up_to_date (self); + g_variant_iter_init (&iter, self->items); while ((child = g_variant_iter_next_value (&iter)) != NULL) { GVariant *hashed_attributes; @@ -750,6 +828,8 @@ secret_file_collection_clear (SecretFileCollection *self, GVariant *child; gboolean removed = FALSE; + ensure_up_to_date (self); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)")); g_variant_iter_init (&items, self->items); while ((child = g_variant_iter_next_value (&items)) != NULL) { @@ -791,6 +871,8 @@ on_replace_contents (GObject *source_object, return; } + self->file_last_modified = get_file_last_modified (self); + g_task_return_boolean (task, TRUE); g_object_unref (task); } -- cgit v1.2.1