/* * e-credentials.c * * Copyright (C) 2011 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 . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "e-data-server-util.h" #include "e-credentials.h" struct _ECredentialsPrivate { GHashTable *keys; GHashTable *peek_keys; }; static gboolean key_equal (gconstpointer str1, gconstpointer str2) { g_return_val_if_fail (str1 != NULL, FALSE); g_return_val_if_fail (str2 != NULL, FALSE); if (str1 == str2) return TRUE; return g_ascii_strcasecmp (str1, str2) == 0; } /** * e_credentials_new: * * FIXME: Document me. * * Since: 3.2 **/ ECredentials * e_credentials_new (void) { ECredentials *credentials; credentials = g_new0 (ECredentials, 1); credentials->priv = g_new0 (ECredentialsPrivate, 1); credentials->priv->keys = g_hash_table_new_full (g_str_hash, key_equal, g_free, (GDestroyNotify) e_credentials_util_safe_free_string); credentials->priv->peek_keys = g_hash_table_new_full (g_str_hash, key_equal, g_free, (GDestroyNotify) e_credentials_util_safe_free_string); return credentials; } /** * e_credentials_new_strv: * * FIXME: Document me. * * Since: 3.2 **/ ECredentials * e_credentials_new_strv (const gchar * const *keys) { ECredentials *credentials; gint ii; /* Expects @keys as NULL terminated list of strings * "key:encoded_value". The same can be returned from * e_credentials_to_strv (). */ g_return_val_if_fail (keys != NULL, NULL); credentials = e_credentials_new (); for (ii = 0; keys[ii]; ii++) { const gchar *key = keys[ii], *sep; sep = strchr (key, ':'); /* skip empty and invalid values */ if (sep) g_hash_table_insert (credentials->priv->keys, g_strndup (key, sep - key), g_strdup (sep + 1)); } return credentials; } /** * e_credentials_new_args: * * FIXME: Document me. * * Since: 3.2 **/ ECredentials * e_credentials_new_args (const gchar *key, ...) { ECredentials *credentials; va_list va; /* NULL-terminated list of string pairs ; value is * in a clear form. */ g_return_val_if_fail (key != NULL, NULL); credentials = e_credentials_new (); va_start (va, key); while (key) { const gchar *value = va_arg (va, const gchar *); if (key && *key && value && *value) e_credentials_set (credentials, key, value); key = va_arg (va, const gchar *); } va_end (va); return credentials; } static void copy_keys_cb (gpointer key, gpointer value, gpointer hash_table) { g_hash_table_insert (hash_table, g_strdup (key), g_strdup (value)); } /** * e_credentials_new_clone: * * FIXME: Document me. * * Since: 3.2 **/ ECredentials * e_credentials_new_clone (const ECredentials *credentials) { ECredentials *res; g_return_val_if_fail (credentials != NULL, NULL); g_return_val_if_fail (credentials->priv != NULL, NULL); g_return_val_if_fail (credentials->priv->keys != NULL, NULL); res = e_credentials_new (); g_hash_table_foreach (credentials->priv->keys, copy_keys_cb, res->priv->keys); return res; } /** * e_credentials_free: * * FIXME: Document me. * * Since: 3.2 **/ void e_credentials_free (ECredentials *credentials) { if (!credentials) return; g_return_if_fail (credentials->priv != NULL); g_hash_table_destroy (credentials->priv->keys); g_hash_table_destroy (credentials->priv->peek_keys); g_free (credentials->priv); g_free (credentials); } static void add_to_array_cb (gpointer key, gpointer value, gpointer ptr_array) { if (key && value && ptr_array) { gchar *str = g_strconcat (key, ":", value, NULL); g_ptr_array_add (ptr_array, e_util_utf8_make_valid (str)); g_free (str); } } /** * e_credentials_to_strv: * @credentials: an #ECredentials * * Returns %NULL-terminated array of strings with keys and encoded values; * To read them back pass this pointer to e_credentials_new(). As it returns * newly allocated string then this should be freed with g_strfreev() when no * longer needed. * * Returns: (transfer full): a %NULL-terminated array of key/value strings * * Since: 3.2 **/ gchar ** e_credentials_to_strv (const ECredentials *credentials) { GPtrArray *array; g_return_val_if_fail (credentials != NULL, NULL); g_return_val_if_fail (credentials->priv != NULL, NULL); g_return_val_if_fail (credentials->priv->keys != NULL, NULL); array = g_ptr_array_sized_new (g_hash_table_size (credentials->priv->keys) + 1); g_hash_table_foreach (credentials->priv->keys, add_to_array_cb, array); /* NULL-terminated */ g_ptr_array_add (array, NULL); return (gchar **) g_ptr_array_free (array, FALSE); } static gchar * encode_string (const gchar *decoded) { gsize len, ii; guchar xval, *copy; gchar *res; if (!decoded || !*decoded) return NULL; copy = (guchar *) g_strdup (decoded); len = strlen ((const gchar *) copy); xval = 17; for (ii = 0; ii < len; ii++) { copy[ii] = copy[ii] ^ xval; xval += 17; } res = g_base64_encode (copy, len); g_free (copy); return res; } static gchar * decode_string (const gchar *encoded) { guchar *data, xval; gsize len = 0, ii; gchar *res; g_return_val_if_fail (encoded != NULL, NULL); g_return_val_if_fail (*encoded, NULL); data = g_base64_decode (encoded, &len); g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (len > 0, NULL); xval = 17; for (ii = 0; ii < len; ii++) { data[ii] = data[ii] ^ xval; xval += 17; } res = g_strndup ((const gchar *) data, len); e_credentials_util_safe_free_string ((gchar *) data); return res; } /** * e_credentials_set: * @credentials: an #ECredentials * @key: a key string * @value: a value string * * Sets value for @key, if @value is %NULL or an empty string then @key is * removed. The value is supposed to be in a clear form (unencoded). * @key cannot contain colon. * * Since: 3.2 **/ void e_credentials_set (ECredentials *credentials, const gchar *key, const gchar *value) { g_return_if_fail (credentials != NULL); g_return_if_fail (credentials->priv != NULL); g_return_if_fail (credentials->priv->keys != NULL); g_return_if_fail (credentials->priv->peek_keys != NULL); g_return_if_fail (key != NULL); g_return_if_fail (*key); g_return_if_fail (strchr (key, ':') == NULL); g_hash_table_remove (credentials->priv->peek_keys, key); if (!value) { g_hash_table_remove (credentials->priv->keys, key); } else { g_hash_table_insert (credentials->priv->keys, g_strdup (key), encode_string (value)); } } /** * e_credentials_get: * * FIXME: Document me. * * Since: 3.2 **/ gchar * e_credentials_get (const ECredentials *credentials, const gchar *key) { const gchar *stored; /* Returned pointer should be freed with * e_credentials_util_safe_free_string() when no longer needed. */ g_return_val_if_fail (credentials != NULL, NULL); g_return_val_if_fail (credentials->priv != NULL, NULL); g_return_val_if_fail (credentials->priv->keys != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (*key, NULL); stored = g_hash_table_lookup (credentials->priv->keys, key); if (!stored) return NULL; return decode_string (stored); } /** * e_credentials_peek: * @credentials: an #ECredentials * @key: a key string * * Peeks at the value for @key, in a clear form. The returned value is valid * until free of the @credentials structure or until the key value is rewritten * by e_credentials_set(). * * Returns: the value for @key * * Since: 3.2 **/ const gchar * e_credentials_peek (ECredentials *credentials, const gchar *key) { gchar *value; g_return_val_if_fail (credentials != NULL, NULL); g_return_val_if_fail (credentials->priv != NULL, NULL); g_return_val_if_fail (credentials->priv->peek_keys != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (*key, NULL); value = g_hash_table_lookup (credentials->priv->peek_keys, key); if (value) return value; value = e_credentials_get (credentials, key); if (value) g_hash_table_insert (credentials->priv->peek_keys, g_strdup (key), value); return value; } struct equal_data { gboolean equal; GHashTable *keys; }; static void check_equal_cb (gpointer key, gpointer value, gpointer user_data) { struct equal_data *ed = user_data; g_return_if_fail (ed != NULL); g_return_if_fail (ed->keys != NULL); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); ed->equal = ed->equal && g_strcmp0 (value, g_hash_table_lookup (ed->keys, key)) == 0; } /** * e_credentials_equal: * @credentials1: an #ECredentials * @credentials2: another #ECredentials * * Returns whether two #ECredential structures contain the same keys with * same values. * * Returns: %TRUE if they are equal, %FALSE otherwise * * Since: 3.2 **/ gboolean e_credentials_equal (const ECredentials *credentials1, const ECredentials *credentials2) { struct equal_data ed; if (!credentials1 && !credentials2) return TRUE; if (credentials1 == credentials2) return TRUE; if (!credentials1 || !credentials2) return FALSE; g_return_val_if_fail (credentials1->priv != NULL, FALSE); g_return_val_if_fail (credentials1->priv->keys != NULL, FALSE); g_return_val_if_fail (credentials2->priv != NULL, FALSE); g_return_val_if_fail (credentials2->priv->keys != NULL, FALSE); if (g_hash_table_size (credentials1->priv->keys) != g_hash_table_size (credentials2->priv->keys)) return FALSE; ed.equal = TRUE; ed.keys = credentials2->priv->keys; g_hash_table_foreach (credentials1->priv->keys, check_equal_cb, &ed); return ed.equal; } /** * e_credentials_equal_keys: * @credentials1: an #ECredentials * @credentials2: another #ECredentials * * Returns whether two #ECredentials structures have the same keys. Key names * are NULL-terminated. * * Returns: %TRUE if the key sets match, %FALSE otherwise * * Since: 3.2 **/ gboolean e_credentials_equal_keys (const ECredentials *credentials1, const ECredentials *credentials2, const gchar *key1, ...) { va_list va; gboolean equal = TRUE; g_return_val_if_fail (credentials1 != NULL, FALSE); g_return_val_if_fail (credentials1->priv != NULL, FALSE); g_return_val_if_fail (credentials1->priv->keys != NULL, FALSE); g_return_val_if_fail (credentials2 != NULL, FALSE); g_return_val_if_fail (credentials2->priv != NULL, FALSE); g_return_val_if_fail (credentials2->priv->keys != NULL, FALSE); g_return_val_if_fail (key1 != NULL, FALSE); va_start (va, key1); while (key1 && equal) { equal = g_strcmp0 (g_hash_table_lookup (credentials1->priv->keys, key1), g_hash_table_lookup (credentials2->priv->keys, key1)) == 0; key1 = va_arg (va, const gchar *); } va_end (va); return equal; } /** * e_credentials_has_key: * @credentials: an #ECredentials * @key: a key string * * Returns whether @credentials contains @key. * * Returns: %TRUE if @credentials contains @key, %FALSE otherwise * * Since: 3.2 **/ gboolean e_credentials_has_key (const ECredentials *credentials, const gchar *key) { g_return_val_if_fail (credentials != NULL, FALSE); g_return_val_if_fail (credentials->priv != NULL, FALSE); g_return_val_if_fail (credentials->priv->keys != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); g_return_val_if_fail (*key, FALSE); return g_hash_table_lookup (credentials->priv->keys, key) != NULL; } /** * e_credentials_keys_size: * @credentials: an #ECredentials * * Returns the number of keys in @credentials. * * Returns: the number of keys in @credentials * * Since: 3.2 **/ guint e_credentials_keys_size (const ECredentials *credentials) { g_return_val_if_fail (credentials != NULL, 0); g_return_val_if_fail (credentials->priv != NULL, 0); g_return_val_if_fail (credentials->priv->keys != NULL, 0); return g_hash_table_size (credentials->priv->keys); } static void gather_key_names (gpointer key, gpointer value, gpointer pslist) { GSList **slist = pslist; g_return_if_fail (pslist != NULL); g_return_if_fail (key != NULL); *slist = g_slist_prepend (*slist, key); } /** * e_credentials_list_keys: * @credentials: an #ECredentials * * Returns a newly-allocated #GSList of key names stored in @credentials. * The key names are internal credentials values and should not be modified * or freed. Free the list with g_slist_free() when no longer needed. * * Returns: (transfer container) (element-type utf8): a newly-allocated #GSList * of key names * * Since: 3.2 **/ GSList * e_credentials_list_keys (const ECredentials *credentials) { GSList *keys = NULL; g_return_val_if_fail (credentials != NULL, NULL); g_return_val_if_fail (credentials->priv != NULL, NULL); g_return_val_if_fail (credentials->priv->keys != NULL, NULL); /* XXX g_hash_table_get_keys() would have been * easier had we used #GList instead. */ g_hash_table_foreach (credentials->priv->keys, gather_key_names, &keys); return g_slist_reverse (keys); } /** * e_credentials_clear: * * FIXME: Document me. * * Since: 3.2 **/ void e_credentials_clear (ECredentials *credentials) { g_return_if_fail (credentials != NULL); g_return_if_fail (credentials->priv != NULL); g_return_if_fail (credentials->priv->keys != NULL); g_return_if_fail (credentials->priv->peek_keys != NULL); g_hash_table_remove_all (credentials->priv->peek_keys); g_hash_table_remove_all (credentials->priv->keys); } /** * e_credentials_clear_peek: * * FIXME: Document me. * * Since: 3.2 **/ void e_credentials_clear_peek (ECredentials *credentials) { g_return_if_fail (credentials != NULL); g_return_if_fail (credentials->priv != NULL); g_return_if_fail (credentials->priv->peek_keys != NULL); g_hash_table_remove_all (credentials->priv->peek_keys); } /** * e_credentials_util_safe_free_string: * * FIXME Document me. * * Since: 3.2 **/ void e_credentials_util_safe_free_string (gchar *str) { if (!str) return; if (*str) memset (str, 0, sizeof (gchar) * strlen (str)); g_free (str); } static struct _PromptFlags { ECredentialsPromptFlags flag_uint; const gchar *flag_string; gboolean is_bit_flag; /* if false, then checked against E_CREDENTIALS_PROMPT_FLAG_REMEMBER_MASK */ } PromptFlags[] = { { E_CREDENTIALS_PROMPT_FLAG_REMEMBER_NEVER, "remember-never", FALSE }, { E_CREDENTIALS_PROMPT_FLAG_REMEMBER_SESSION, "remember-session", FALSE }, { E_CREDENTIALS_PROMPT_FLAG_REMEMBER_FOREVER, "remember-forever", FALSE }, { E_CREDENTIALS_PROMPT_FLAG_SECRET, "secret", TRUE }, { E_CREDENTIALS_PROMPT_FLAG_REPROMPT, "reprompt", TRUE }, { E_CREDENTIALS_PROMPT_FLAG_ONLINE, "online", TRUE }, { E_CREDENTIALS_PROMPT_FLAG_DISABLE_REMEMBER, "disable-remember", TRUE }, { E_CREDENTIALS_PROMPT_FLAG_PASSPHRASE, "passphrase", TRUE } }; /** * e_credentials_util_prompt_flags_to_string: * * FIXME: Document me. * * Since: 3.2 **/ gchar * e_credentials_util_prompt_flags_to_string (guint prompt_flags) { gint ii; guint masked = prompt_flags & E_CREDENTIALS_PROMPT_FLAG_REMEMBER_MASK; GString *str = g_string_new (""); /* Returned pointer can be passed to * e_credentials_util_string_to prompt_flags() to decode * it back to flags. Free returned pointer with g_free(). */ for (ii = 0; ii < G_N_ELEMENTS (PromptFlags); ii++) { const gchar *add = NULL; if (PromptFlags[ii].is_bit_flag) { if ((prompt_flags & PromptFlags[ii].flag_uint) != 0) add = PromptFlags[ii].flag_string; } else if (masked == PromptFlags[ii].flag_uint) { add = PromptFlags[ii].flag_string; } if (!add) continue; if (str->len) g_string_append (str, ","); g_string_append (str, add); } return g_string_free (str, FALSE); } /** * e_credentials_util_string_to_prompt_flags: * * FIXME: Document me. * * Since: 3.2 **/ guint e_credentials_util_string_to_prompt_flags (const gchar *prompt_flags_string) { gchar **strv; gint ii, jj; guint flags = 0; if (!prompt_flags_string || !*prompt_flags_string) return flags; strv = g_strsplit (prompt_flags_string, ",", -1); if (!strv) return flags; for (jj = 0; strv[jj]; jj++) { const gchar *str = strv[jj]; for (ii = 0; ii < G_N_ELEMENTS (PromptFlags); ii++) { if (g_str_equal (PromptFlags[ii].flag_string, str)) { if (PromptFlags[ii].is_bit_flag) flags |= PromptFlags[ii].flag_uint; else flags = (flags & (~E_CREDENTIALS_PROMPT_FLAG_REMEMBER_MASK)) | PromptFlags[ii].flag_uint; } } } g_strfreev (strv); return flags; }