/* * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /** * SECTION: e-secret-store * @include: libedataserver/libedataserver.h * @short_description: Interface to store secrets * * The e-secret-store API provides an interface to store, * lookup and delete secrets from the keyring. **/ #include "evolution-data-server-config.h" #include #ifdef G_OS_WIN32 #include #include #else #include #endif #include "e-data-server-util.h" #include "e-secret-store.h" #ifdef G_OS_WIN32 G_LOCK_DEFINE_STATIC (secrets_file); static GHashTable *session_secrets = NULL; #define SECRETS_SECTION "Secrets" static gchar * encode_secret (const gchar *secret) { return g_base64_encode ((const guchar *) secret, strlen (secret)); } static gchar * decode_secret (const gchar *secret) { guchar *decoded; gchar *tmp; gsize len = 0; decoded = g_base64_decode (secret, &len); if (!decoded || !len) { g_free (decoded); return NULL; } tmp = g_strndup ((const gchar *) decoded, len); g_free (decoded); return tmp; } static gchar * get_secrets_filename (void) { return g_build_filename (e_get_user_config_dir (), "secrets", NULL); } static GKeyFile * read_secrets_file (GError **error) { gchar *filename; GKeyFile *secrets; secrets = g_key_file_new (); filename = get_secrets_filename (); if (g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { if (!g_key_file_load_from_file (secrets, filename, G_KEY_FILE_NONE, error)) { g_key_file_free (secrets); secrets = NULL; } } g_free (filename); return secrets; } static gboolean store_secrets_file (GKeyFile *secrets, GError **error) { gchar *content, *filename; gsize length; gboolean success; g_return_val_if_fail (secrets != NULL, FALSE); if (!g_file_test (e_get_user_config_dir (), G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { if (g_mkdir_with_parents (e_get_user_config_dir (), 0700) == -1) { g_set_error_literal ( error, G_FILE_ERROR, g_file_error_from_errno (errno), g_strerror (errno)); return FALSE; } } content = g_key_file_to_data (secrets, &length, error); if (!content) return FALSE; filename = get_secrets_filename (); success = g_file_set_contents (filename, content, length, error); g_free (filename); g_free (content); return success; } static gboolean e_win32_secret_store_secret_sync (const gchar *uid, const gchar *secret, gboolean permanently, GError **error) { GKeyFile *secrets; gboolean success; g_return_val_if_fail (uid != NULL, FALSE); G_LOCK (secrets_file); if (permanently) { secrets = read_secrets_file (error); success = secrets != NULL; if (secrets) { gchar *encoded; encoded = secret && *secret ? encode_secret (secret) : g_strdup (secret); g_key_file_set_string (secrets, SECRETS_SECTION, uid, encoded); success = store_secrets_file (secrets, error); g_key_file_free (secrets); g_free (encoded); } } else { gchar *encoded; if (!session_secrets) session_secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) e_util_safe_free_string); encoded = secret && *secret ? encode_secret (secret) : g_strdup (secret); if (!encoded) g_hash_table_remove (session_secrets, uid); else g_hash_table_insert (session_secrets, g_strdup (uid), encoded); } G_UNLOCK (secrets_file); return success; } static gchar * e_win32_secret_lookup_secret_sync (const gchar *uid, GError **error) { GKeyFile *secrets; gchar *secret = NULL; g_return_val_if_fail (uid != NULL, NULL); G_LOCK (secrets_file); if (session_secrets) { const gchar *encoded; encoded = g_hash_table_lookup (session_secrets, uid); if (encoded) secret = decode_secret (encoded); } if (!secret) { secrets = read_secrets_file (error); if (secrets) { gchar *tmp; tmp = g_key_file_get_string (secrets, SECRETS_SECTION, uid, NULL); if (tmp) { secret = *tmp ? decode_secret (tmp) : g_strdup (""); g_free (tmp); } g_key_file_free (secrets); } } G_UNLOCK (secrets_file); return secret; } static gboolean e_win32_secret_delete_secret_sync (const gchar *uid, GError **error) { GKeyFile *secrets; gboolean success = FALSE; g_return_val_if_fail (uid != NULL, FALSE); G_LOCK (secrets_file); if (session_secrets) { success = g_hash_table_remove (session_secrets, uid); } secrets = read_secrets_file (error); if (secrets) { success = TRUE; if (g_key_file_remove_key (secrets, SECRETS_SECTION, uid, NULL)) { success = store_secrets_file (secrets, error); } g_key_file_free (secrets); } G_UNLOCK (secrets_file); return success; } #else /* G_OS_WIN32 */ #define KEYRING_ITEM_ATTRIBUTE_UID "e-source-uid" #define KEYRING_ITEM_ATTRIBUTE_ORIGIN "eds-origin" #ifdef DBUS_SERVICES_PREFIX #define ORIGIN_KEY DBUS_SERVICES_PREFIX "." PACKAGE #else #define ORIGIN_KEY PACKAGE #endif static SecretSchema password_schema = { "org.gnome.Evolution.Data.Source", SECRET_SCHEMA_DONT_MATCH_NAME, { { KEYRING_ITEM_ATTRIBUTE_UID, SECRET_SCHEMA_ATTRIBUTE_STRING }, { KEYRING_ITEM_ATTRIBUTE_ORIGIN, SECRET_SCHEMA_ATTRIBUTE_STRING }, { NULL, 0 } } }; #endif /* G_OS_WIN32 */ /** * e_secret_store_store_sync: * @uid: a unique identifier of the secret * @secret: the secret to store * @label: human readable description of the secret * @permanently: store permanently or just for the session * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Stores the @secret for the @uid. * * If @permanently is %TRUE, the secret is stored in the default keyring. * Otherwise the secret is stored in the memory-only session keyring. If * an error occurs, the function sets @error and returns %FALSE. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.18 **/ gboolean e_secret_store_store_sync (const gchar *uid, const gchar *secret, const gchar *label, gboolean permanently, GCancellable *cancellable, GError **error) { gboolean success; #ifndef G_OS_WIN32 const gchar *collection; #endif g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (secret != NULL, FALSE); #ifndef G_OS_WIN32 if (permanently) collection = SECRET_COLLECTION_DEFAULT; else collection = SECRET_COLLECTION_SESSION; #endif #ifdef G_OS_WIN32 success = e_win32_secret_store_secret_sync (uid, secret, permanently, error); #else success = secret_password_store_sync ( &password_schema, collection, label, secret, cancellable, error, KEYRING_ITEM_ATTRIBUTE_UID, uid, KEYRING_ITEM_ATTRIBUTE_ORIGIN, ORIGIN_KEY, NULL); #endif return success; } /** * e_secret_store_lookup_sync: * @uid: a unique identifier of the secret * @out_secret: (out): return location for the secret, or %NULL * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Looks up a secret for the @uid. Both the default and session keyrings * are queried. * * Note the boolean return value indicates whether the lookup operation * itself completed successfully, not whether the secret was found. If * no secret was found, the function will set @out_secret to %NULL, * but still return %TRUE. If an error occurs, the function sets @error * and returns %FALSE. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.18 **/ gboolean e_secret_store_lookup_sync (const gchar *uid, gchar **out_secret, GCancellable *cancellable, GError **error) { gchar *temp = NULL; gboolean success = TRUE; GError *local_error = NULL; g_return_val_if_fail (uid != NULL, FALSE); #ifdef G_OS_WIN32 temp = e_win32_secret_lookup_secret_sync (uid, &local_error); #else temp = secret_password_lookup_sync ( &password_schema, cancellable, &local_error, KEYRING_ITEM_ATTRIBUTE_UID, uid, NULL); #endif if (local_error != NULL) { g_warn_if_fail (temp == NULL); g_propagate_error (error, local_error); success = FALSE; } else if (out_secret != NULL) { *out_secret = temp; /* takes ownership */ } else { e_util_safe_free_string (temp); } return success; } /** * e_secret_store_delete_sync: * @uid: a unique identifier of the secret * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Deletes the secret for @uid from either the default keyring or * session keyring. * * Note the boolean return value indicates whether the delete operation * itself completed successfully, not whether the secret was found and * deleted. If no such secret was found, the function will still return * %TRUE. If an error occurs, the function sets @error and returns %FALSE. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.18 **/ gboolean e_secret_store_delete_sync (const gchar *uid, GCancellable *cancellable, GError **error) { gboolean success = TRUE; GError *local_error = NULL; g_return_val_if_fail (uid != NULL, FALSE); #ifdef G_OS_WIN32 e_win32_secret_delete_secret_sync (uid, &local_error); #else /* The return value indicates whether any passwords were removed, * not whether the operation completed successfully. So we have * to check the GError directly. */ secret_password_clear_sync ( &password_schema, cancellable, &local_error, KEYRING_ITEM_ATTRIBUTE_UID, uid, NULL); #endif if (local_error != NULL) { g_propagate_error (error, local_error); success = FALSE; } return success; }