/*
* 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;
}