The MySQL Credential Cache
authorwarlord <warlord@cb58f699-b61c-0410-a6fe-9272a202ed29>
Thu, 26 Jun 2003 20:25:55 +0000 (20:25 +0000)
committerwarlord <warlord@cb58f699-b61c-0410-a6fe-9272a202ed29>
Thu, 26 Jun 2003 20:25:55 +0000 (20:25 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@550 cb58f699-b61c-0410-a6fe-9272a202ed29

Makefile.am
configure.ac
shib-mysql-ccache/.cvsignore [new file with mode: 0644]
shib-mysql-ccache/Makefile.am [new file with mode: 0644]
shib-mysql-ccache/shib-mysql-ccache.cpp [new file with mode: 0644]

index f27229c..21c057c 100644 (file)
@@ -42,5 +42,5 @@ WANT_SUBDIRS = @WANT_SUBDIRS@
 SUBDIRS = $(WANT_SUBDIRS)
 
 DIST_SUBDIRS = doc oncrpc shib schemas configs shib-target shar test \
-       mod_shire mod_shibrm siterefresh
+       mod_shire mod_shibrm siterefresh shib-mysql-ccache
 
index bfe0e6b..7fbdc4a 100644 (file)
@@ -336,6 +336,139 @@ AC_CONFIG_FILES([mod_shire/Makefile mod_shibrm/Makefile])
 
 AM_CONDITIONAL(DO_APXS_INSTALL,test -n "$APXS_INSTALL")
 
+#
+# Implement the checks of the MySQL Credential Cache
+#
+# 1) Assume the user wants MySQL; if it's not found then just continue without
+# 2) If the user specifically requested Mysql, look for it and ERROR if not found
+# 3) If the user specifically requested no-mysql, don't build it.
+#
+
+AC_CONFIG_FILES([shib-mysql-ccache/Makefile])
+
+# determine whether we should enable the mysql ccache
+enable_mysql=default
+AC_ARG_ENABLE([mysql],
+       AC_HELP_STRING([--disable-mysql], [disable the MySQL Credential Cache]))
+if test "x$enable_mysql" = "x" ; then
+   enable_mysql=yes
+fi
+
+# Are we trying to build MySQL?
+AC_MSG_CHECKING(whether to build the MySQL ccache)
+if test "$enable_mysql" = "yes" -o "$enable_mysql" = "default" ; then
+   build_mysql=yes
+   AC_MSG_RESULT(yes)
+else
+   build_mysql=no
+   AC_MSG_RESULT(no)
+fi
+
+# If we're trying to build MySQL, try to find the mysql_config program,
+# verify we've got mysql >= 4, and make sure we can build with mysqld
+if test "$build_mysql" = "yes" ; then
+   mysql_dir=""
+   AC_ARG_WITH(mysql,
+       AC_HELP_STRING([--with-mysql=PATH], [directory where mysql is installed]),
+           [if test "$with_mysql" = no ; then
+               AC_MSG_ERROR([Try running --disable-mysql instead.])
+             elif test "$with_mysql" != yes ; then
+               mysql_dir="$with_mysql/bin"
+             fi ])
+
+   # Try to find the mysql_config program
+   AC_PATH_PROG(MYSQL_CONFIG, mysql_config, no, $mysql_dir $PATH )
+
+   if test "$MYSQL_CONFIG" = no ; then
+      if test "$enable_mysql" = yes ; then
+        AC_MSG_ERROR(Cannot find mysql_config)
+      else
+        AC_MSG_WARN(MySQL not found.  skipping.)
+      fi
+   fi
+fi
+
+# if we found mysql_config then build_mysql is 'yes'
+if test "$MYSQL_CONFIG" != no ; then
+   AC_MSG_CHECKING(for mysql version >= 4)
+   mysql_version=`$MYSQL_CONFIG --version`
+   mysql_major_version=`echo $mysql_version | awk -F. '{print $1}'`
+   mysql_version_ok=yes
+   if test $mysql_major_version -lt 4 ; then
+     mysql_version_ok=no
+     if test "$enable_mysql" = yes ; then
+       AC_MSG_ERROR(You need MySQL version >= 4, found $mysql_version)
+     fi
+     AC_MSG_RESULT(no.. skipping MySQL)
+   else
+     AC_MSG_RESULT(yes)
+   fi
+fi
+
+# if mysql_version_ok is 'yes', then we've made it this far.. ;)
+if test "$mysql_version_ok" = "yes" ; then
+   AC_MSG_CHECKING(for embedded-MySQL libraries)
+   MYSQL_LIBS=`$MYSQL_CONFIG --libmysqld-libs`
+   if test $? != 0 ; then
+      found_mysql=no
+      if test "$enable_mysql" = yes ; then
+        AC_MSG_ERROR(Could not find the MySQL Embedded-server libraries.)
+      else
+        AC_MSG_RESULT(no.. skipping MySQL)
+      fi
+   else
+      found_mysql=yes
+      AC_MSG_RESULT(yes)
+   fi
+fi
+
+# if found_mysql=yes then test that we can actually build mysql
+if test "$found_mysql" = yes ; then
+   MYSQL_CFLAGS=`$MYSQL_CONFIG --cflags`
+   MYSQL_CFLAGS=`eval echo $MYSQL_CFLAGS`
+   MYSQL_LIBS=`eval echo $MYSQL_LIBS`
+
+   save_CPPFLAGS="$CPPFLAGS"
+   CPPFLAGS="$CPPFLAGS $MYSQL_CFLAGS"
+
+   AC_CHECK_HEADER([mysql.h], [have_mysql_h=yes], [have_mysql_h=no])
+   if test "$have_mysql_h" = no -a "$enable_mysql" = yes ; then
+     AC_MSG_ERROR(unable to find MySQL header files)
+   fi
+
+   if test "$have_mysql_h" = yes ; then
+      save_LIBS="$LIBS"
+      LIBS="$LIBS $MYSQL_LIBS"
+      AC_MSG_CHECKING(if we can link againt mysql)
+      AC_TRY_LINK(
+        [#include <mysql.h>],
+        [mysql_server_init(0, NULL, NULL)],
+       [have_mysql_libs=yes],
+       [have_mysql_libs=no])
+      LIBS="$save_LIBS"
+
+      if test "$have_mysql_libs" = no ; then
+         if "$enable_mysql" = yes ; then
+            AC_MSG_ERROR([unable to link with MySQL Embedded Server Library])
+        else
+           AC_MSG_RESULT(no.  skipping MySQL)
+        fi
+      fi
+   fi
+
+   CPPFLAGS="$save_CPPFLAGS"
+fi
+
+# if have_mysql_libs=yes then go ahead with building MySQL
+if test "$have_mysql_libs" = yes ; then
+   # this AC_MSG_RESULT is from above!
+   AC_MSG_RESULT(yes)
+   WANT_SUBDIRS="$WANT_SUBDIRS shib-mysql-ccache"
+   AC_SUBST(MYSQL_CFLAGS)
+   AC_SUBST(MYSQL_LIBS)
+fi
+
+
 AC_SUBST(WANT_SUBDIRS)
 
 if test -n "$APXS_CC" && test "$APXS_CC" != "$CC" ; then
diff --git a/shib-mysql-ccache/.cvsignore b/shib-mysql-ccache/.cvsignore
new file mode 100644 (file)
index 0000000..3bfbb72
--- /dev/null
@@ -0,0 +1,6 @@
+Makefile.in
+Makefile
+.libs
+.deps
+*.lo
+*.la
diff --git a/shib-mysql-ccache/Makefile.am b/shib-mysql-ccache/Makefile.am
new file mode 100644 (file)
index 0000000..5cff5b3
--- /dev/null
@@ -0,0 +1,18 @@
+AUTOMAKE_OPTIONS = foreign
+
+lib_LTLIBRARIES = libshib-mysql-ccache.la
+
+AM_CFLAGS = $(MYSQL_CFLAGS)
+AM_CXXFLAGS = $(AM_CFLAGS)
+
+libshib_mysql_ccache_la_LIBADD = \
+       ../shib-target/libshib-target.la \
+       ../shib/libshib.la \
+       $(MYSQL_LIBS)
+
+libshib_mysql_ccache_la_SOURCES = \
+       shib-mysql-ccache.cpp
+
+# this is different from the project version
+# http://sources.redhat.com/autobook/autobook/autobook_91.html
+libshib_mysql_ccache_la_LDFLAGS = -version-info 1:0:0
diff --git a/shib-mysql-ccache/shib-mysql-ccache.cpp b/shib-mysql-ccache/shib-mysql-ccache.cpp
new file mode 100644 (file)
index 0000000..d5f01d4
--- /dev/null
@@ -0,0 +1,557 @@
+/*
+ * shib-mysql-ccache.cpp: Shibboleth Credential Cache using MySQL.
+ *
+ * Created by: Derek Atkins <derek@ihtfp.com>
+ *
+ * $Id$
+ */
+
+/* This file is loosely based off the Shibboleth Credential Cache.
+ * This plug-in is designed as a two-layer cache.  Layer 1, the
+ * long-term cache, stores data in a MySQL embedded database.  The
+ * data stored in layer 1 is only the session id (cookie), the
+ * "posted" SAML statement (expanded into an XML string), and usage
+ * timestamps.
+ *
+ * Short-term data is cached in memory as SAML objects in the layer 2
+ * cache.  Data like Attribute Authority assertions are stored in
+ * the layer 2 cache.
+ */
+
+#ifndef WIN32
+# include <unistd.h>
+#endif
+
+#include <shib-target/shib-target.h>
+#include <shib-target/ccache-utils.h>
+#include <shib/shib-threads.h>
+#include <log4cpp/Category.hh>
+
+#include <sstream>
+#include <stdexcept>
+
+#include <mysql.h>
+
+#ifdef HAVE_LIBDMALLOCXX
+#include <dmalloc.h>
+#endif
+
+using namespace std;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+
+class ShibMySQLCCache;
+class ShibMySQLCCacheEntry : public CCacheEntry
+{
+public:
+  ShibMySQLCCacheEntry(const char *, CCacheEntry*, ShibMySQLCCache*);
+  ~ShibMySQLCCacheEntry() {}
+
+  virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource)
+       { return m_cacheEntry->getAssertions(resource); }
+  virtual void preFetch(Resource& resource, int prefetch_window)
+       { return m_cacheEntry->preFetch(resource, prefetch_window); }
+  virtual bool isSessionValid(time_t lifetime, time_t timeout);
+  virtual const char* getClientAddress()
+       { return m_cacheEntry->getClientAddress(); }
+  virtual const char* getSerializedStatement()
+       { return m_cacheEntry->getSerializedStatement(); }
+  virtual const SAMLAuthenticationStatement* getStatement()
+       { return m_cacheEntry->getStatement(); }
+  virtual void release()
+       { m_cacheEntry->release(); delete this; }
+
+private:
+  void touch();
+
+  ShibMySQLCCache* m_cache;
+  CCacheEntry *m_cacheEntry;
+  string m_key;
+};
+
+class ShibMySQLCCache : public CCache
+{
+public:
+  ShibMySQLCCache();
+  virtual ~ShibMySQLCCache();
+
+  virtual SAMLBinding* getBinding(const XMLCh* bindingProt)
+       { return m_cache->getBinding(bindingProt); }
+  virtual CCacheEntry* find(const char* key);
+  virtual void insert(const char* key, SAMLAuthenticationStatement *s,
+                     const char *client_addr);
+  virtual void remove(const char* key);
+  virtual void thread_init();
+
+  void cleanup();
+  MYSQL* getMYSQL();
+
+  log4cpp::Category* log;
+
+private:
+  CCache* m_cache;
+  ThreadKey* m_mysql;
+
+  static void* cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
+  CondWait* shutdown_wait;
+  bool shutdown;
+  Thread* cleanup_thread;
+
+  bool initialized;
+
+  void createDatabase(MYSQL*, int major, int minor);
+  void getVersion(MYSQL*, int* major_p, int* minor_p);
+  void mysqlInit(void);
+};
+
+// Forward declarations
+extern "C" void shib_mysql_destroy_handle(void* data);
+
+/*************************************************************************
+ * The CCache here talks to a MySQL database.  The database stores
+ * three items: the cookie (session key index), the lastAccess time, and
+ * the SAMLAuthenticationStatement.  All other access is performed
+ * through the memory cache provided by shibboleth.
+ */
+
+MYSQL* ShibMySQLCCache::getMYSQL()
+{
+  void* data = m_mysql->getData();
+  return (MYSQL*)data;
+}
+
+void ShibMySQLCCache::thread_init()
+{
+  saml::NDC ndc("open_db");
+
+  // Connect to the database
+  MYSQL* mysql = mysql_init(NULL);
+  if (!mysql) {
+    log->error("mysql_init failed");
+    mysql_close(mysql);
+    throw runtime_error("mysql_init()");
+  }
+
+  if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
+    if (initialized) {
+      log->crit("mysql_real_connect failed: %s", mysql_error(mysql));
+      throw runtime_error("mysql_real_connect");
+
+    } else {
+      log->info("mysql_real_connect failed: %s.  Trying to create",
+               mysql_error(mysql));
+
+      // This will throw a runtime error if it fails.
+      createDatabase(mysql, 0, 0);
+    }
+  }
+
+  int major = -1, minor = -1;
+  getVersion (mysql, &major, &minor);
+
+  // Make sure we've got the right version
+  if (major != 0 && minor != 0) {
+    log->crit("Invalid database version: %d.%d", major, minor);
+    throw runtime_error("Invalid Database version");
+  }
+
+  // We're all set.. Save off the handle for this thread.
+  m_mysql->setData((void*)mysql);
+}
+
+ShibMySQLCCache::ShibMySQLCCache()
+{
+  saml::NDC ndc("shibmysql::ShibMySQLCCache");
+
+  m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
+  string ctx = "shibmysql::ShibMySQLCCache";
+  log = &(log4cpp::Category::getInstance(ctx));
+
+  initialized = false;
+  mysqlInit();
+  thread_init();
+  initialized = true;
+
+  m_cache = CCache::getInstance("memory");
+
+  // Initialize the cleanup thread
+  shutdown_wait = CondWait::create();
+  shutdown = false;
+  cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
+}
+
+ShibMySQLCCache::~ShibMySQLCCache()
+{
+  shutdown = true;
+  shutdown_wait->signal();
+  cleanup_thread->join(NULL);
+
+  delete m_cache;
+  delete m_mysql;
+
+  // Shutdown MySQL
+  mysql_server_end();
+}
+
+CCacheEntry* ShibMySQLCCache::find(const char* key)
+{
+  saml::NDC ndc("mysql::find");
+  CCacheEntry* res = m_cache->find(key);
+  if (!res) {
+
+    log->debug("Looking in database...");
+
+    // nothing cached; see if this exists in the database
+    ostringstream q;
+    q << "SELECT addr,statement FROM state WHERE cookie='" << key << "' LIMIT 1";
+
+    MYSQL_RES* rows;
+    MYSQL* mysql = getMYSQL();
+    if (mysql_query(mysql, q.str().c_str()))
+      log->error("Error searching for %s: %s", key, mysql_error(mysql));
+
+    rows = mysql_store_result(mysql);
+
+    // Nope, doesn't exist.
+    if (!rows)
+      return NULL;
+
+    // Make sure we got 1 and only 1 rows.
+    if (mysql_num_rows(rows) != 1) {
+      log->error("Select returned wrong number of rows: %d", mysql_num_rows(rows));
+      mysql_free_result(rows);
+      return NULL;
+    }
+
+    log->debug("Match found.  Parsing...");
+    // Pull apart the row and process the results
+    MYSQL_ROW row = mysql_fetch_row(rows);
+    istringstream str(row[1]);
+    SAMLAuthenticationStatement *s = NULL;
+
+    // Try to parse the AuthStatement
+    try {
+      s = new SAMLAuthenticationStatement(str);
+    } catch (...) {
+      mysql_free_result(rows);
+      throw;
+    }
+
+    // Insert it into the memory cache
+    if (s)
+      m_cache->insert(key, s, row[0]);
+
+    // Free the results, and then re-run the 'find' query
+    mysql_free_result(rows);
+    res = m_cache->find(key);
+    if (!res)
+      return NULL;
+  }
+
+  return new ShibMySQLCCacheEntry(key, res, this);
+}
+
+void ShibMySQLCCache::insert(const char* key, SAMLAuthenticationStatement *s,
+                           const char *client_addr)
+{
+  saml::NDC ndc("mysql::insert");
+  ostringstream os;
+  os << *s;
+
+  ostringstream q;
+  q << "INSERT INTO state VALUES('" << key << "', NOW(), '" << client_addr
+    << "', '" << os.str() << "')";
+
+  log->debug("Query: %s", q.str().c_str());
+
+  // Add it to the memory cache
+  m_cache->insert(key, s, client_addr);
+
+  // then add it to the database
+  MYSQL* mysql = getMYSQL();
+  if (mysql_query(mysql, q.str().c_str()))
+    log->error("Error inserting %s: %s", key, mysql_error(mysql));
+}
+
+void ShibMySQLCCache::remove(const char* key)
+{
+  saml::NDC ndc("mysql::remove");
+
+  // Remove the cached version
+  m_cache->remove(key);
+
+  // Remove from the database
+  ostringstream q;
+  q << "DELETE FROM state WHERE cookie='" << key << "'";
+  MYSQL* mysql = getMYSQL();
+  if (mysql_query(mysql, q.str().c_str()))
+    log->error("Error deleting entry %s: %s", key, mysql_error(mysql));
+}
+
+void ShibMySQLCCache::cleanup()
+{
+  Mutex* mutex = Mutex::create();
+  saml::NDC ndc("mysql::cleanup()");
+
+  thread_init();
+
+  ShibTargetConfig& config = ShibTargetConfig::getConfig();
+  ShibINI& ini = config.getINI();
+
+  int rerun_timer = 0;
+  int timeout_life = 0;
+
+  string tag;
+  if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
+    rerun_timer = atoi(tag.c_str());
+
+  // search for 'mysql-cache-timeout' and then the regular cache timeout
+  if (ini.get_tag (SHIBTARGET_SHAR, "mysql-cache-timeout", true, &tag))
+    timeout_life = atoi(tag.c_str());
+  else if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
+    timeout_life = atoi(tag.c_str());
+
+  if (rerun_timer <= 0)
+    rerun_timer = 300;         // rerun every 5 minutes
+
+  if (timeout_life <= 0)
+    timeout_life = 28800;      // timeout after 8 hours
+
+  mutex->lock();
+
+  MYSQL* mysql = getMYSQL();
+
+  while (shutdown == false) {
+    shutdown_wait->timedwait(mutex, rerun_timer);
+
+    if (shutdown == true)
+      break;
+
+    // Find all the entries in the database that haven't been used
+    // recently In particular, find all entries that have not been
+    // accessed in 'timeout_life' seconds.
+    ostringstream q;
+    q << "SELECT cookie FROM state WHERE " <<
+      "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(atime) >= " << timeout_life;
+
+    MYSQL_RES *rows;
+    if (mysql_query(mysql, q.str().c_str()))
+      log->error("Error searching for old items: %s", mysql_error(mysql));
+
+    rows = mysql_store_result(mysql);
+    if (!rows)
+      continue;
+
+    if (mysql_num_fields(rows) != 1) {
+      log->error("Wrong number of rows, 1 != %d", mysql_num_fields(rows));
+      mysql_free_result(rows);
+      continue;
+    }
+
+    // For each row, remove the entry from the database.
+    MYSQL_ROW row;
+    while ((row = mysql_fetch_row(rows)) != NULL)
+      remove(row[0]);
+
+    mysql_free_result(rows);
+  }
+
+  mutex->unlock();
+  delete mutex;
+  thread_end();
+  Thread::exit(NULL);
+}
+
+void* ShibMySQLCCache::cleanup_fcn(void* cache_p)
+{
+  ShibMySQLCCache* cache = (ShibMySQLCCache*)cache_p;
+
+  // First, let's block all signals
+  Thread::mask_all_signals();
+
+  // Now run the cleanup process.
+  cache->cleanup();
+  return NULL;
+}
+
+void ShibMySQLCCache::createDatabase(MYSQL* mysql, int major, int minor)
+{
+  log->info("Creating database.");
+
+  MYSQL* ms = NULL;
+  try {
+    ms = mysql_init(NULL);
+    if (!ms) {
+      log->crit("mysql_init failed");
+      throw new ShibTargetException();
+    }
+
+    if (!mysql_real_connect(ms, NULL, NULL, NULL, NULL, 0, NULL, 0)) {
+      log->crit("cannot open DB file to create DB: %s", mysql_error(ms));
+      throw new ShibTargetException();
+    }
+
+    if (mysql_query(ms, "CREATE DATABASE shar")) {
+      log->crit("cannot create shar database: %s", mysql_error(ms));
+      throw new ShibTargetException();
+    }
+
+    if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
+      log->crit("cannot open SHAR database");
+      throw new ShibTargetException();
+    }
+
+    mysql_close(ms);
+    
+  } catch (ShibTargetException &e) {
+    if (ms)
+      mysql_close(ms);
+    mysql_close(mysql);
+    throw runtime_error("mysql_real_connect");
+  }
+
+  // Now create the tables if they don't exist
+  log->info("Creating database tables.");
+
+  if (mysql_query(mysql, "CREATE TABLE version (major INT, minor INT)"))
+    log->error ("Error creating version: %s", mysql_error(mysql));
+
+  if (mysql_query(mysql,
+                 "CREATE TABLE state (cookie VARCHAR(64) PRIMARY KEY, "
+                 "atime DATETIME, addr VARCHAR(128), statement TEXT)"))
+    log->error ("Error creating state: %s", mysql_error(mysql));
+
+  ostringstream q;
+  q << "INSERT INTO version VALUES(" << major << "," << minor << ")";
+  if (mysql_query(mysql, q.str().c_str()))
+    log->error ("Error setting version: %s", mysql_error(mysql));
+}
+
+void ShibMySQLCCache::getVersion(MYSQL* mysql, int* major_p, int* minor_p)
+{
+  // grab the version number from the database
+  if (mysql_query(mysql, "SELECT * FROM version"))
+    log->error ("Error reading version: %s", mysql_error(mysql));
+
+  MYSQL_RES* rows = mysql_store_result(mysql);
+  if (rows) {
+    if (mysql_num_rows(rows) == 1 && mysql_num_fields(rows) == 2)  {
+      MYSQL_ROW row = mysql_fetch_row(rows);
+
+      int major = row[0] ? atoi(row[0]) : -1;
+      int minor = row[1] ? atoi(row[1]) : -1;
+      log->debug("opening database version %d.%d", major, minor);
+      
+      mysql_free_result (rows);
+
+      *major_p = major;
+      *minor_p = minor;
+      return;
+
+    } else {
+      // Wrong number of rows or wrong number of fields...
+
+      log->crit("Houston, we've got a problem with the database..");
+      mysql_free_result (rows);
+      throw runtime_error("Database version verification failed");
+    }
+  }
+  log->crit("MySQL Read Failed in version verificatoin");
+  throw runtime_error("MySQL Read Failed");
+}
+
+void ShibMySQLCCache::mysqlInit(void)
+{
+  log->info ("Opening MySQL Database");
+
+  // Setup the argument array
+  vector<string> arg_array;
+  string tag;
+
+  tag = SHIBTARGET_SHAR;
+  arg_array.push_back(tag);
+
+  // grab any MySQL parameters from the config file
+  ShibTargetConfig& config = ShibTargetConfig::getConfig();
+  ShibINI& ini = config.getINI();
+
+  if (ini.exists("mysql")) {
+    ShibINI::Iterator* iter = ini.tag_iterator("mysql");
+
+    for (const string* str = iter->begin(); str; str = iter->next()) {
+      string arg = ini.get("mysql", *str);
+      arg_array.push_back(arg);
+    }
+    delete iter;
+  }
+
+  // Compute the argument array
+  int arg_count = arg_array.size();
+  const char* args[arg_count];
+  for (int i = 0; i < arg_count; i++)
+    args[i] = arg_array[i].c_str();
+
+  // Initialize MySQL with the arguments
+  mysql_server_init(arg_count, (char **)args, NULL);
+}  
+
+/*************************************************************************
+ * The CCacheEntry here is mostly a wrapper around the "memory"
+ * cacheentry provided by shibboleth.  The only difference is that we
+ * intercept the isSessionValid() so that we can "touch()" the
+ * database if the session is still valid.
+ */
+
+ShibMySQLCCacheEntry::ShibMySQLCCacheEntry(const char* key, CCacheEntry *entry,
+                                        ShibMySQLCCache* cache)
+{
+  m_cacheEntry = entry;
+  m_key = key;
+  m_cache = cache;
+}
+
+bool ShibMySQLCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
+{
+  bool res = m_cacheEntry->isSessionValid(lifetime, timeout);
+  if (res == true)
+    touch();
+  return res;
+}
+
+void ShibMySQLCCacheEntry::touch()
+{
+  ostringstream q;
+  q << "UPDATE state SET atime=NOW() WHERE cookie='" << m_key << "'";
+
+  MYSQL* mysql = m_cache->getMYSQL();
+  if (mysql_query(mysql, q.str().c_str()))
+    m_cache->log->error("Error updating timestamp on %s: %s",
+                       m_key.c_str(), mysql_error(mysql));
+}
+
+/*************************************************************************
+ * The registration functions here...
+ */
+
+extern "C" CCache* new_mysql_ccache(void)
+{
+  return new ShibMySQLCCache();
+}
+
+extern "C" int saml_extension_init(void* context)
+{
+  // register this ccache type
+  CCache::registerFactory("mysql", &new_mysql_ccache);
+  return 0;
+}
+
+/*************************************************************************
+ * Local Functions
+ */
+
+extern "C" void shib_mysql_destroy_handle(void* data)
+{
+  MYSQL* mysql = (MYSQL*) data;
+  mysql_close(mysql);
+}