[libgda] Handle authentification data in GdaQuarkList



commit d7bbac1acbb8b352e848f398e310704be3c77fc7
Author: Vivien Malerba <malerba gnome-db org>
Date:   Tue May 1 19:58:19 2012 +0200

    Handle authentification data in GdaQuarkList
    
    by keeping it in a mangled version, and by avoiding cleartext
    version to be swapped. (Clear text version is created when value
    is requested)

 configure.ac              |    2 +
 doc/C/libgda-sections.txt |    1 +
 libgda/gda-quark-list.c   |  310 ++++++++++++++++++++++++++++++++++++++++++---
 libgda/gda-quark-list.h   |   10 +-
 libgda/libgda.symbols     |    1 +
 tests/.gitignore          |    1 +
 tests/Makefile.am         |    8 +-
 tests/test-quark-list.c   |   96 ++++++++++++++
 8 files changed, 404 insertions(+), 25 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 10522e0..c716b85 100644
--- a/configure.ac
+++ b/configure.ac
@@ -64,6 +64,8 @@ AC_CHECK_SIZEOF(unsigned int,0)
 AC_CHECK_SIZEOF(unsigned long int,0)
 AC_CHECK_TYPE(uint8_t, unsigned char)
 AC_CHECK_FUNCS(localtime_r localtime_s)
+AC_CHECK_HEADER(sys/mman.h,
+    AC_CHECK_FUNC(mlock, [AC_DEFINE(USE_MLOCK, 1, [Use POSIX memory locking])]))
 
 GDA_BUILDDATE=`date '+%F'`
 AC_SUBST(GDA_BUILDDATE, $GDA_BUILDDATE)
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index 74351f1..8c7aed4 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -526,6 +526,7 @@ gda_quark_list_free
 gda_quark_list_clear
 gda_quark_list_add_from_string
 gda_quark_list_find
+gda_quark_list_protect_values
 gda_quark_list_remove
 gda_quark_list_foreach
 <SUBSECTION Standard>
diff --git a/libgda/gda-quark-list.c b/libgda/gda-quark-list.c
index 5b56e08..10b8abb 100644
--- a/libgda/gda-quark-list.c
+++ b/libgda/gda-quark-list.c
@@ -25,9 +25,29 @@
 
 #include <libgda/gda-quark-list.h>
 #include <libgda/gda-util.h>
+#include <string.h>
+
+#ifdef USE_MLOCK
+#include <sys/mman.h>
+#endif
+
+#define RANDOM_BLOB_SIZE 1024
+static gchar random_blob [RANDOM_BLOB_SIZE] = {0};
+static void ensure_static_blob_filled (void);
+
+typedef struct {
+	guint  offset;/* offset in random_blob XOR from */
+	gchar *pvalue; /* XORed value, not 0 terminated */
+	gchar *cvalue; /* clear value, memory allocated with malloc() and mlock() */
+} ProtectedValue;
+
+static ProtectedValue *protected_value_new (gchar *cvalue);
+static void protected_value_free (ProtectedValue *pvalue);
+static void protected_value_xor (ProtectedValue *pvalue, gboolean to_clear);
 
 struct _GdaQuarkList {
 	GHashTable *hash_table;
+	GHashTable *hash_protected;
 };
 
 GType gda_quark_list_get_type (void)
@@ -46,6 +66,25 @@ GType gda_quark_list_get_type (void)
  */
 
 static void
+ensure_static_blob_filled (void)
+{
+	if (random_blob [0] == 0) {
+		guint i;
+		for (i = 0; i < RANDOM_BLOB_SIZE; i++) {
+			random_blob [i]	= g_random_int_range (1, 255);
+			/*g_print ("%02x ", (guchar) random_blob [i]);*/
+		}
+#ifdef G_OS_WIN32
+		VirtualLock (random_blob, sizeof (gchar) * RANDOM_BLOB_SIZE);
+#else
+#ifdef USE_MLOCK
+		mlock (random_blob, sizeof (gchar) * RANDOM_BLOB_SIZE);
+#endif
+#endif
+	}
+}
+
+static void
 copy_hash_pair (gpointer key, gpointer value, gpointer user_data)
 {
 	g_hash_table_insert ((GHashTable *) user_data,
@@ -53,6 +92,117 @@ copy_hash_pair (gpointer key, gpointer value, gpointer user_data)
 			     g_strdup ((const char *) value));
 }
 
+guint
+protected_get_length (gchar *str, guint offset)
+{
+	gchar *ptr;
+	guint i;
+	ensure_static_blob_filled ();
+	for (i = 0, ptr = str; i < RANDOM_BLOB_SIZE - offset - 1; i++, ptr++) {
+		gchar c0, c1;
+		c0 = *ptr;
+		c1 = c0 ^ random_blob [offset + i];
+		if (!c1)
+			break;
+	}
+	return i;
+}
+
+static void
+protected_value_xor (ProtectedValue *pvalue, gboolean to_clear)
+{
+	if (to_clear) {
+		if (! pvalue->cvalue) {
+			guint i, l;
+			ensure_static_blob_filled ();
+			l = protected_get_length (pvalue->pvalue, pvalue->offset);
+			pvalue->cvalue = malloc (sizeof (gchar) * (l + 1));
+			for (i = 0; i < l; i++)
+				pvalue->cvalue [i] = pvalue->pvalue [i] ^ random_blob [pvalue->offset + i];
+			pvalue->cvalue [l] = 0;
+#ifdef G_OS_WIN32
+			VirtualLock (pvalue->cvalue, sizeof (gchar) * (l + 1));
+#else
+#ifdef USE_MLOCK
+			mlock (pvalue->cvalue, sizeof (gchar) * (l + 1));
+#endif
+#endif
+			/*g_print ("Unmangled [%s]\n", pvalue->cvalue);*/
+		}
+	}
+	else {
+		if (pvalue->cvalue) {
+			/*g_print ("Mangled [%s]\n", pvalue->cvalue);*/
+			guint i;
+			for (i = 0; ; i++) {
+				gchar c;
+				c = pvalue->cvalue[i];
+				pvalue->cvalue[i] = g_random_int_range (1, 255);
+				if (!c)
+					break;
+			}
+#ifdef G_OS_WIN32
+			VirtualUnLock (pvalue->cvalue, sizeof (gchar*) * (i + 1));
+#else
+#ifdef USE_MLOCK
+			munlock (pvalue->cvalue, sizeof (gchar*) * (i + 1));
+#endif
+#endif
+			free (pvalue->cvalue);
+			pvalue->cvalue = NULL;
+		}
+	}
+}
+
+static void
+copy_hash_pair_protected (gpointer key, gpointer value, gpointer user_data)
+{
+	ProtectedValue *p1, *p2;
+	guint l;
+	p1 = (ProtectedValue*) value;
+	p2 = g_new0 (ProtectedValue, 1);
+	l = protected_get_length (p1->pvalue, p1->offset);
+	p2->pvalue = g_new (gchar, l + 1);
+	memcpy (p2->pvalue, p1->pvalue, l + 1);
+	p2->offset = p1->offset;
+	p2->cvalue = NULL;
+	g_hash_table_insert ((GHashTable *) user_data,
+			     g_strdup ((const char *) key), p2);
+}
+
+static ProtectedValue *
+protected_value_new (gchar *cvalue)
+{
+	ProtectedValue *pvalue;
+	guint i, l;
+	l = strlen (cvalue);
+	if (l >= RANDOM_BLOB_SIZE) {
+		g_warning ("Value too big to protect!");
+		return NULL;
+	}
+
+	/*g_print ("Initially mangled [%s]\n", cvalue);*/
+	ensure_static_blob_filled ();
+	pvalue = g_new (ProtectedValue, 1);
+	pvalue->offset = g_random_int_range (0, RANDOM_BLOB_SIZE - l - 2);
+	pvalue->pvalue = g_new (gchar, l + 1);
+	pvalue->cvalue = NULL;
+	for (i = 0; i <= l; i++) {
+		pvalue->pvalue [i] = cvalue [i] ^ random_blob [pvalue->offset + i];
+		cvalue [i] = g_random_int_range (0, 255);
+	}
+	return pvalue;
+}
+
+static void
+protected_value_free (ProtectedValue *pvalue)
+{
+	g_free (pvalue->pvalue);
+	if (pvalue->cvalue)
+		protected_value_xor (pvalue, FALSE);
+	g_free (pvalue);
+}
+
 /**
  * gda_quark_list_new:
  *
@@ -71,7 +221,8 @@ gda_quark_list_new (void)
 	GdaQuarkList *qlist;
 
 	qlist = g_new0 (GdaQuarkList, 1);
-	qlist->hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	qlist->hash_table = NULL;
+	qlist->hash_protected = NULL;
 
 	return qlist;
 }
@@ -116,7 +267,10 @@ gda_quark_list_clear (GdaQuarkList *qlist)
 {
 	g_return_if_fail (qlist != NULL);
 	
-	g_hash_table_remove_all (qlist->hash_table);
+	if (qlist->hash_table)
+		g_hash_table_remove_all (qlist->hash_table);
+	if (qlist->hash_protected)
+		g_hash_table_remove_all (qlist->hash_protected);
 }
 
 /**
@@ -130,7 +284,10 @@ gda_quark_list_free (GdaQuarkList *qlist)
 {
 	g_return_if_fail (qlist != NULL);
 
-	g_hash_table_destroy (qlist->hash_table);
+	if (qlist->hash_table)
+		g_hash_table_destroy (qlist->hash_table);
+	if (qlist->hash_protected)
+		g_hash_table_destroy (qlist->hash_protected);
 
 	g_free (qlist);
 }
@@ -152,12 +309,33 @@ gda_quark_list_copy (GdaQuarkList *qlist)
 	g_return_val_if_fail (qlist != NULL, NULL);
 	
 	new_qlist = gda_quark_list_new ();
-	g_hash_table_foreach (qlist->hash_table,
-			      copy_hash_pair,
-			      new_qlist->hash_table);
+	if (qlist->hash_table) {
+		new_qlist->hash_table = g_hash_table_new_full (g_str_hash,
+							       g_str_equal,
+							       g_free, g_free);
+		g_hash_table_foreach (qlist->hash_table, copy_hash_pair, new_qlist->hash_table);
+	}
+	if (qlist->hash_protected) {
+		new_qlist->hash_protected = g_hash_table_new_full (g_str_hash,
+								   g_str_equal,
+								   g_free,
+								   (GDestroyNotify) protected_value_free);
+		g_hash_table_foreach (qlist->hash_protected, copy_hash_pair_protected,
+				      new_qlist->hash_protected);
+	}
 	return new_qlist;
 }
 
+static gboolean
+name_is_protected (const gchar *name)
+{
+	if (!g_ascii_strncasecmp (name, "pass", 4) ||
+	    !g_ascii_strncasecmp (name, "username", 8))
+		return TRUE;
+	else
+		return FALSE;
+}
+
 /**
  * gda_quark_list_add_from_string
  * @qlist: a #GdaQuarkList.
@@ -215,8 +393,37 @@ gda_quark_list_add_from_string (GdaQuarkList *qlist, const gchar *string, gboole
 					g_strstrip (value);
 					gda_rfc1738_decode (value);
 				}
-				g_hash_table_insert (qlist->hash_table, 
-						     (gpointer) name, (gpointer) value);
+
+				if (! name_is_protected (name)) {
+					if (!qlist->hash_table)
+						qlist->hash_table = g_hash_table_new_full (g_str_hash,
+											   g_str_equal,
+											   g_free, g_free);
+					g_hash_table_insert (qlist->hash_table, 
+							     (gpointer) name, (gpointer) value);
+				}
+				else {
+					ProtectedValue *pvalue;
+					pvalue = protected_value_new (value);
+					if (pvalue) {
+						if (!qlist->hash_protected)
+							qlist->hash_protected = g_hash_table_new_full (g_str_hash,
+												       g_str_equal,
+												       g_free,
+												       (GDestroyNotify) protected_value_free);
+						g_hash_table_insert (qlist->hash_protected, 
+								     (gpointer) name, (gpointer) pvalue);
+						g_free (value); /* has been mangled */
+					}
+					else {
+						if (!qlist->hash_table)
+							qlist->hash_table = g_hash_table_new_full (g_str_hash,
+												   g_str_equal,
+												   g_free, g_free);
+						g_hash_table_insert (qlist->hash_table, 
+								     (gpointer) name, (gpointer) value);
+					}
+				}
 				g_free (pair);
 			}
 			else
@@ -232,21 +439,34 @@ gda_quark_list_add_from_string (GdaQuarkList *qlist, const gchar *string, gboole
  * @qlist: a #GdaQuarkList.
  * @name: the name of the value to search for.
  *
- * Searches for the value identified by @name in the given #GdaQuarkList.
+ * Searches for the value identified by @name in the given #GdaQuarkList. For protected values
+ * (authentification data), don't forget to call gda_quark_list_protect_values() when you
+ * don't need them anymore (when needed again, they will be unmangled again).
  *
- * Returns: the value associated with the given key if found, or %NULL
- * if not found.
+ * Returns: the value associated with the given key if found, or %NULL if not found.
  */
 const gchar *
 gda_quark_list_find (GdaQuarkList *qlist, const gchar *name)
 {
-	gchar *value;
-
-	g_return_val_if_fail (qlist != NULL, NULL);
-	g_return_val_if_fail (name != NULL, NULL);
-
-	value = g_hash_table_lookup (qlist->hash_table, name);
-	return (const gchar *) value;
+	gchar *value = NULL;
+	g_return_val_if_fail (qlist, NULL);
+	g_return_val_if_fail (name, NULL);
+
+	if (qlist->hash_table)
+		value = g_hash_table_lookup (qlist->hash_table, name);
+	if (value)
+		return value;
+
+	ProtectedValue *pvalue = NULL;
+	if (qlist->hash_protected)
+		pvalue = g_hash_table_lookup (qlist->hash_protected, name);
+	if (pvalue) {
+		if (! pvalue->cvalue)
+			protected_value_xor (pvalue, TRUE);
+		return pvalue->cvalue;
+	}
+	else
+		return NULL;
 }
 
 /**
@@ -259,10 +479,31 @@ gda_quark_list_find (GdaQuarkList *qlist, const gchar *name)
 void
 gda_quark_list_remove (GdaQuarkList *qlist, const gchar *name)
 {
+	gboolean removed = FALSE;
 	g_return_if_fail (qlist != NULL);
 	g_return_if_fail (name != NULL);
 
-	g_hash_table_remove (qlist->hash_table, name);
+	if (qlist->hash_table && g_hash_table_remove (qlist->hash_table, name))
+		removed = TRUE;
+	if (!removed && qlist->hash_protected)
+		g_hash_table_remove (qlist->hash_protected, name);
+}
+
+typedef struct {
+	gpointer user_data;
+	GHFunc func;
+} PFuncData;
+
+static void
+p_foreach (gchar *key, ProtectedValue *pvalue, PFuncData *data)
+{
+	if (pvalue->cvalue)
+		data->func ((gpointer) key, (gpointer) pvalue->cvalue, data->user_data);
+	else {
+		protected_value_xor (pvalue, TRUE);
+		data->func ((gpointer) key, (gpointer) pvalue->cvalue, data->user_data);
+		protected_value_xor (pvalue, FALSE);
+	}
 }
 
 /**
@@ -277,7 +518,34 @@ gda_quark_list_remove (GdaQuarkList *qlist, const gchar *name)
 void
 gda_quark_list_foreach (GdaQuarkList *qlist, GHFunc func, gpointer user_data)
 {
-	g_return_if_fail (qlist != NULL);
+	g_return_if_fail (qlist);
 
-	g_hash_table_foreach (qlist->hash_table, func, user_data);
+	if (qlist->hash_table)
+		g_hash_table_foreach (qlist->hash_table, func, user_data);
+	if (qlist->hash_protected)
+		g_hash_table_foreach (qlist->hash_protected, (GHFunc) p_foreach, user_data);
+}
+
+static void
+protect_foreach (G_GNUC_UNUSED gchar *key, ProtectedValue *pvalue, G_GNUC_UNUSED gpointer data)
+{
+	if (pvalue && pvalue->cvalue)
+		protected_value_xor (pvalue, FALSE);
+}
+
+/**
+ * gda_quark_list_protect_values:
+ * @qlist: a #GdaQuarkList
+ *
+ * Call this function to get rid of the clear version of the value associated to
+ * @name.
+ *
+ * Since: 5.2.0
+ */
+void
+gda_quark_list_protect_values (GdaQuarkList *qlist)
+{
+	g_return_if_fail (qlist);
+	if (qlist->hash_protected)
+		g_hash_table_foreach (qlist->hash_protected, (GHFunc) protect_foreach, NULL);
 }
diff --git a/libgda/gda-quark-list.h b/libgda/gda-quark-list.h
index 9b34a7b..897a2a2 100644
--- a/libgda/gda-quark-list.h
+++ b/libgda/gda-quark-list.h
@@ -40,8 +40,12 @@ typedef struct _GdaQuarkList GdaQuarkList;
  * @stability: Stable
  * @see_also:
  *
- * This object is used mainly by database providers' implementations to parse connection
- * strings into lists of KEY=VALUE pairs.
+ * This object is used to store KEY=VALUE pairs. It is mainly used internally by Libgda to store connection
+ * parameters.
+ *
+ * Authentification values are kept in a mangled form in memory, and unmangled when
+ * they are requested using gda_quark_list_find(), and when you don't need them anymore,
+ * call gda_quark_list_protect_values() to remove the unmangled version.
  */
 
 GType         gda_quark_list_get_type        (void) G_GNUC_CONST;
@@ -54,6 +58,8 @@ void          gda_quark_list_add_from_string (GdaQuarkList *qlist,
 					      const gchar *string,
 					      gboolean cleanup);
 const gchar  *gda_quark_list_find            (GdaQuarkList *qlist, const gchar *name);
+void          gda_quark_list_protect_values  (GdaQuarkList *qlist);
+
 void          gda_quark_list_remove          (GdaQuarkList *qlist, const gchar *name);
 void          gda_quark_list_clear           (GdaQuarkList *qlist);
 void          gda_quark_list_foreach         (GdaQuarkList *qlist, GHFunc func, gpointer user_data);
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index a10f481..6815eee 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -540,6 +540,7 @@
 	gda_quark_list_get_type
 	gda_quark_list_new
 	gda_quark_list_new_from_string
+	gda_quark_list_protect_values
 	gda_quark_list_remove
 	gda_repetitive_statement_append_set
 	gda_repetitive_statement_get_all_sets
diff --git a/tests/.gitignore b/tests/.gitignore
index a56365c..232b794 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,3 +5,4 @@ test-identifiers-quotes
 test-sql-builder
 test-connection-string-split
 test-input-parsers
+test-quark-list
\ No newline at end of file
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f47077a..dec1cce 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,8 +1,8 @@
 noinst_LTLIBRARIES = libgda-test-5.0.la
 
 TESTS_ENVIRONMENT = GDA_TOP_SRC_DIR="$(abs_top_srcdir)" GDA_TOP_BUILD_DIR="$(abs_top_builddir)"
-TESTS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder test-connection-string-split test-input-parsers
-check_PROGRAMS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder test-connection-string-split test-input-parsers
+TESTS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder test-connection-string-split test-input-parsers test-quark-list
+check_PROGRAMS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder test-connection-string-split test-input-parsers test-quark-list
 
 if ENABLE_VALA_EXTENSIONS
     VALA_EXTENSIONS= vala
@@ -92,4 +92,8 @@ test_input_parsers_LDADD = \
         $(top_builddir)/libgda/libgda-5.0.la \
         $(COREDEPS_LIBS)
 
+test_quark_list_LDADD = \
+        $(top_builddir)/libgda/libgda-5.0.la \
+        $(COREDEPS_LIBS)
+
 EXTRA_DIST = dbstruct.xml
diff --git a/tests/test-quark-list.c b/tests/test-quark-list.c
new file mode 100644
index 0000000..086ea8c
--- /dev/null
+++ b/tests/test-quark-list.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <libgda/libgda.h>
+#include <string.h>
+
+guint
+test_quark (GdaQuarkList *ql, guint *out_ntests)
+{
+	const gchar *tmp;
+	guint nfailed = 0;
+	guint ntests = 0;
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "PARAM");
+	if (!tmp || strcmp (tmp, "value"))
+		nfailed++;
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "PASSWORD");
+	if (!tmp || strcmp (tmp, "*mypass*"))
+		nfailed++;
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "PASSWORD");
+	if (!tmp || strcmp (tmp, "*mypass*"))
+		nfailed++;
+
+	gda_quark_list_protect_values (ql);
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "PASSWORD");
+	if (!tmp || strcmp (tmp, "*mypass*"))
+		nfailed++;
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "USERNAME");
+	if (!tmp || strcmp (tmp, "dirch"))
+		nfailed++;
+
+	gda_quark_list_remove (ql, "PASSWORD");
+
+	ntests ++;
+	tmp = gda_quark_list_find (ql, "PASSWORD");
+	if (tmp)
+		nfailed++;
+
+	gda_quark_list_protect_values (ql);
+
+	if (out_ntests)
+		*out_ntests = ntests;
+	return nfailed;
+}
+
+int
+main (int argc, char** argv)
+{
+	GdaQuarkList *ql, *ql2;
+	guint nfailed = 0;
+	guint ntests = 0;
+	guint tmp;
+
+	ql = gda_quark_list_new_from_string ("PARAM=value;PASSWORD=*mypass*;USERNAME=dirch");
+	ql2 = gda_quark_list_copy (ql);
+	nfailed = test_quark (ql, &ntests);
+	nfailed += test_quark (ql2, &tmp);
+	ntests += tmp;
+
+	/* out */
+	gda_quark_list_free (ql);
+	gda_quark_list_free (ql2);
+
+	if (nfailed == 0) {
+		g_print ("Ok (%d tests passed)\n", ntests);
+		return EXIT_SUCCESS;
+	}
+	else {
+		g_print ("Failed (%d tests failed out of %d)\n", nfailed, ntests);
+		return EXIT_FAILURE;
+	}
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]