Added replay cache to plugin.
authorScott Cantor <cantor.2@osu.edu>
Thu, 19 May 2005 20:02:05 +0000 (20:02 +0000)
committerScott Cantor <cantor.2@osu.edu>
Thu, 19 May 2005 20:02:05 +0000 (20:02 +0000)
shib-mysql-ccache/shib-mysql-ccache.cpp

index 9b48b72..d42fe35 100644 (file)
@@ -8,14 +8,10 @@
 
 /* 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.
+ * long-term cache, stores data in a MySQL embedded database.
  *
  * 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.
+ * cache.
  */
 
 // eventually we might be able to support autoconf via cygwin...
@@ -72,6 +68,11 @@ using namespace log4cpp;
   "response TEXT," \
   "statement TEXT)"
 
+#define REPLAY_TABLE \
+  "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
+  "expires TIMESTAMP, " \
+  "INDEX (expires))"
+
 static const XMLCh Argument[] =
 { chLatin_A, chLatin_r, chLatin_g, chLatin_u, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chNull };
 static const XMLCh cleanupInterval[] =
@@ -85,74 +86,24 @@ static const XMLCh mysqlTimeout[] =
 static const XMLCh storeAttributes[] =
 { chLatin_s, chLatin_t, chLatin_o, chLatin_r, chLatin_e, chLatin_A, chLatin_t, chLatin_t, chLatin_r, chLatin_i, chLatin_b, chLatin_u, chLatin_t, chLatin_e, chLatin_s, chNull };
 
-class ShibMySQLCCache;
-class ShibMySQLCCacheEntry : public ISessionCacheEntry
+class MySQLBase
 {
 public:
-  ShibMySQLCCacheEntry(const char* key, ISessionCacheEntry* entry, ShibMySQLCCache* cache)
-    : m_cacheEntry(entry), m_key(key), m_cache(cache), m_responseId(NULL) {}
-  ~ShibMySQLCCacheEntry() {if (m_responseId) XMLString::release(&m_responseId);}
-
-  virtual void lock() {}
-  virtual void unlock() { m_cacheEntry->unlock(); delete this; }
-  virtual bool isValid(time_t lifetime, time_t timeout) const;
-  virtual const char* getClientAddress() const { return m_cacheEntry->getClientAddress(); }
-  virtual ShibProfile getProfile() const { return m_cacheEntry->getProfile(); }
-  virtual const char* getProviderId() const { return m_cacheEntry->getProviderId(); }
-  virtual const SAMLAuthenticationStatement* getAuthnStatement() const { return m_cacheEntry->getAuthnStatement(); }
-  virtual CachedResponse getResponse();
+  MySQLBase(const DOMElement* e);
+  virtual ~MySQLBase();
 
-private:
-  bool touch() const;
+  void thread_init();
+  void thread_end() {}
 
-  ShibMySQLCCache* m_cache;
-  ISessionCacheEntry* m_cacheEntry;
-  string m_key;
-  XMLCh* m_responseId;
-};
-
-class ShibMySQLCCache : public ISessionCache
-{
-public:
-  ShibMySQLCCache(const DOMElement* e);
-  virtual ~ShibMySQLCCache();
-
-  virtual void thread_init();
-  virtual void thread_end() {}
-
-  virtual string generateKey() const {return m_cache->generateKey();}
-  virtual ISessionCacheEntry* find(const char* key, const IApplication* application);
-  virtual void insert(
-    const char* key,
-    const IApplication* application,
-    const char* client_addr,
-    ShibProfile profile,
-    const char* providerId,
-    saml::SAMLAuthenticationStatement* s,
-    saml::SAMLResponse* r=NULL,
-    const shibboleth::IRoleDescriptor* source=NULL,
-    time_t created=0,
-    time_t accessed=0
-    );
-  virtual void remove(const char* key);
-
-  void cleanup();
   MYSQL* getMYSQL() const;
   bool repairTable(MYSQL*&, const char* table);
 
   log4cpp::Category* log;
-  bool m_storeAttributes;
 
-private:
-  ISessionCache* m_cache;
+protected:
   ThreadKey* m_mysql;
   const DOMElement* m_root; // can only use this during initialization
 
-  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);
@@ -161,22 +112,42 @@ private:
 };
 
 // Forward declarations
-extern "C" void shib_mysql_destroy_handle(void* data);
 void mysqlInit(const DOMElement* e, Category& log);
 
-/*************************************************************************
- * 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.
- */
+extern "C" void shib_mysql_destroy_handle(void* data)
+{
+  MYSQL* mysql = (MYSQL*) data;
+  if (mysql) mysql_close(mysql);
+}
 
-MYSQL* ShibMySQLCCache::getMYSQL() const
+MySQLBase::MySQLBase(const DOMElement* e) : m_root(e)
+{
+#ifdef _DEBUG
+  saml::NDC ndc("MySQLBase");
+#endif
+  log = &(Category::getInstance("shibmysql.MySQLBase"));
+
+  m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
+
+  initialized = false;
+  mysqlInit(e,*log);
+  thread_init();
+  initialized = true;
+}
+
+MySQLBase::~MySQLBase()
+{
+  thread_end();
+
+  delete m_mysql;
+}
+
+MYSQL* MySQLBase::getMYSQL() const
 {
   return (MYSQL*)m_mysql->getData();
 }
 
-void ShibMySQLCCache::thread_init()
+void MySQLBase::thread_init()
 {
 #ifdef _DEBUG
   saml::NDC ndc("thread_init");
@@ -187,56 +158,289 @@ void ShibMySQLCCache::thread_init()
   if (!mysql) {
     log->error("mysql_init failed");
     mysql_close(mysql);
-    throw SAMLException("ShibMySQLCCache::thread_init(): mysql_init() failed");
+    throw SAMLException("MySQLBase::thread_init(): mysql_init() failed");
   }
 
   if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
     if (initialized) {
       log->crit("mysql_real_connect failed: %s", mysql_error(mysql));
       mysql_close(mysql);
-      throw SAMLException("ShibMySQLCCache::thread_init(): mysql_real_connect() failed");
+      throw SAMLException("MySQLBase::thread_init(): mysql_real_connect() failed");
     } else {
       log->info("mysql_real_connect failed: %s.  Trying to create", mysql_error(mysql));
 
-      // This will throw an exception if it fails.
-      createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
-    }
-  }
+      // This will throw an exception if it fails.
+      createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
+    }
+  }
+
+  int major = -1, minor = -1;
+  getVersion (mysql, &major, &minor);
+
+  // Make sure we've got the right version
+  if (major != PLUGIN_VER_MAJOR || minor != PLUGIN_VER_MINOR) {
+   
+    // If we're capable, try upgrading on the fly...
+    if (major == 0  || major == 1) {
+       upgradeDatabase(mysql);
+    }
+    else {
+        mysql_close(mysql);
+        log->crit("Unknown database version: %d.%d", major, minor);
+        throw SAMLException("MySQLBase::thread_init(): Unknown database version");
+    }
+  }
+
+  // We're all set.. Save off the handle for this thread.
+  m_mysql->setData(mysql);
+}
+
+bool MySQLBase::repairTable(MYSQL*& mysql, const char* table)
+{
+  string q = string("REPAIR TABLE ") + table;
+  if (mysql_query(mysql, q.c_str())) {
+    log->error("Error repairing table %s: %s", table, mysql_error(mysql));
+    return false;
+  }
+
+  // seems we have to recycle the connection to get the thread to keep working
+  // other threads seem to be ok, but we should monitor that
+  mysql_close(mysql);
+  m_mysql->setData(NULL);
+  thread_init();
+  mysql=getMYSQL();
+  return true;
+}
+
+void MySQLBase::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 SAMLException("ShibMySQLCCache::createDatabase(): mysql_init failed");
+    }
+
+    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 SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect failed");
+    }
+
+    if (mysql_query(ms, "CREATE DATABASE shar")) {
+      log->crit("cannot create shar database: %s", mysql_error(ms));
+      throw SAMLException("ShibMySQLCCache::createDatabase(): create db cmd failed");
+    }
+
+    if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
+      log->crit("cannot open SHAR database");
+      throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect to plugin db failed");
+    }
+
+    mysql_close(ms);
+    
+  }
+  catch (SAMLException&) {
+    if (ms)
+      mysql_close(ms);
+    mysql_close(mysql);
+    throw;
+  }
+
+  // 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));
+    throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
+  }
+
+  if (mysql_query(mysql,STATE_TABLE)) {
+    log->error ("Error creating state table: %s", mysql_error(mysql));
+    throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
+  }
+
+  if (mysql_query(mysql,REPLAY_TABLE)) {
+    log->error ("Error creating replay table: %s", mysql_error(mysql));
+    throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
+  }
+
+  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));
+    throw SAMLException("ShibMySQLCCache::createDatabase(): version insert failed");
+  }
+}
+
+void MySQLBase::upgradeDatabase(MYSQL* mysql)
+{
+    if (mysql_query(mysql, "DROP TABLE state")) {
+        log->error("Error dropping old session state table: %s", mysql_error(mysql));
+    }
+
+    if (mysql_query(mysql,STATE_TABLE)) {
+        log->error ("Error creating state table: %s", mysql_error(mysql));
+        throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
+    }
+
+    if (mysql_query(mysql,REPLAY_TABLE)) {
+        log->error ("Error creating replay table: %s", mysql_error(mysql));
+        throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating replay table");
+    }
+
+    ostringstream q;
+    q << "UPDATE version SET major = " << PLUGIN_VER_MAJOR;
+    if (mysql_query(mysql, q.str().c_str())) {
+        log->error ("Error updating version: %s", mysql_error(mysql));
+        throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error updating version");
+    }
+}
+
+void MySQLBase::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 SAMLException("ShibMySQLCCache::getVersion(): version verification failed");
+    }
+  }
+  log->crit("MySQL Read Failed in version verificatoin");
+  throw SAMLException("ShibMySQLCCache::getVersion(): error reading version");
+}
+
+static void mysqlInit(const DOMElement* e, Category& log)
+{
+  static bool done = false;
+  if (done) {
+    log.info("MySQL embedded server already initialized");
+    return;
+  }
+  log.info("initializing MySQL embedded server");
+
+  // Setup the argument array
+  vector<string> arg_array;
+  arg_array.push_back("shibboleth");
+
+  // grab any MySQL parameters from the config file
+  e=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,Argument);
+  while (e) {
+      auto_ptr_char arg(e->getFirstChild()->getNodeValue());
+      if (arg.get())
+          arg_array.push_back(arg.get());
+      e=saml::XML::getNextSiblingElement(e,shibtarget::XML::SHIBTARGET_NS,Argument);
+  }
+
+  // Compute the argument array
+  int arg_count = arg_array.size();
+  const char** args=new const char*[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);
+
+  delete[] args;
+  done = true;
+}  
+
+class ShibMySQLCCache;
+class ShibMySQLCCacheEntry : public ISessionCacheEntry
+{
+public:
+  ShibMySQLCCacheEntry(const char* key, ISessionCacheEntry* entry, ShibMySQLCCache* cache)
+    : m_cacheEntry(entry), m_key(key), m_cache(cache), m_responseId(NULL) {}
+  ~ShibMySQLCCacheEntry() {if (m_responseId) XMLString::release(&m_responseId);}
+
+  virtual void lock() {}
+  virtual void unlock() { m_cacheEntry->unlock(); delete this; }
+  virtual bool isValid(time_t lifetime, time_t timeout) const;
+  virtual const char* getClientAddress() const { return m_cacheEntry->getClientAddress(); }
+  virtual ShibProfile getProfile() const { return m_cacheEntry->getProfile(); }
+  virtual const char* getProviderId() const { return m_cacheEntry->getProviderId(); }
+  virtual const SAMLAuthenticationStatement* getAuthnStatement() const { return m_cacheEntry->getAuthnStatement(); }
+  virtual CachedResponse getResponse();
+
+private:
+  bool touch() const;
+
+  ShibMySQLCCache* m_cache;
+  ISessionCacheEntry* m_cacheEntry;
+  string m_key;
+  XMLCh* m_responseId;
+};
+
+class ShibMySQLCCache : public MySQLBase, virtual public ISessionCache
+{
+public:
+  ShibMySQLCCache(const DOMElement* e);
+  virtual ~ShibMySQLCCache();
+
+  virtual void thread_init() {MySQLBase::thread_init();}
+  virtual void thread_end() {MySQLBase::thread_end();}
+
+  virtual string generateKey() const {return m_cache->generateKey();}
+  virtual ISessionCacheEntry* find(const char* key, const IApplication* application);
+  virtual void insert(
+    const char* key,
+    const IApplication* application,
+    const char* client_addr,
+    ShibProfile profile,
+    const char* providerId,
+    saml::SAMLAuthenticationStatement* s,
+    saml::SAMLResponse* r=NULL,
+    const shibboleth::IRoleDescriptor* source=NULL,
+    time_t created=0,
+    time_t accessed=0
+    );
+  virtual void remove(const char* key);
+
+  virtual void cleanup();
 
-  int major = -1, minor = -1;
-  getVersion (mysql, &major, &minor);
+  bool m_storeAttributes;
 
-  // Make sure we've got the right version
-  if (major != PLUGIN_VER_MAJOR || minor != PLUGIN_VER_MINOR) {
-   
-    // If we're capable, try upgrading on the fly...
-    if (major == 0  || major == 1) {
-       upgradeDatabase(mysql);
-    }
-    else {
-        mysql_close(mysql);
-        log->crit("Unknown database version: %d.%d", major, minor);
-        throw SAMLException("ShibMySQLCCache::thread_init(): Unknown database version");
-    }
-  }
+private:
+  ISessionCache* m_cache;
+  CondWait* shutdown_wait;
+  bool shutdown;
+  Thread* cleanup_thread;
 
-  // We're all set.. Save off the handle for this thread.
-  m_mysql->setData(mysql);
-}
+  static void* cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
+};
 
-ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e) : m_root(e), m_storeAttributes(false)
+ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e) : MySQLBase(e), m_storeAttributes(false)
 {
 #ifdef _DEBUG
-  saml::NDC ndc("shibmysql::ShibMySQLCCache");
+  saml::NDC ndc("ShibMySQLCCache");
 #endif
 
-  m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
-  log = &(Category::getInstance("shibmysql::ShibMySQLCCache"));
+  log = &(Category::getInstance("shibmysql.SessionCache"));
 
-  initialized = false;
-  mysqlInit(e,*log);
-  thread_init();
-  initialized = true;
+  shutdown_wait = CondWait::create();
+  shutdown = false;
 
   m_cache = dynamic_cast<ISessionCache*>(
       SAMLConfig::getConfig().getPlugMgr().newPlugin(
@@ -250,8 +454,6 @@ ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e) : m_root(e), m_storeAttrib
     m_storeAttributes=true;
 
   // Initialize the cleanup thread
-  shutdown_wait = CondWait::create();
-  shutdown = false;
   cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
 }
 
@@ -261,18 +463,13 @@ ShibMySQLCCache::~ShibMySQLCCache()
   shutdown_wait->signal();
   cleanup_thread->join(NULL);
 
-  thread_end();
   delete m_cache;
-  delete m_mysql;
-
-  // Shutdown MySQL
-  mysql_server_end();
 }
 
 ISessionCacheEntry* ShibMySQLCCache::find(const char* key, const IApplication* application)
 {
 #ifdef _DEBUG
-  saml::NDC ndc("ShibMySQLCCache::find");
+  saml::NDC ndc("find");
 #endif
 
   ISessionCacheEntry* res = m_cache->find(key, application);
@@ -415,7 +612,7 @@ void ShibMySQLCCache::insert(
     )
 {
 #ifdef _DEBUG
-  saml::NDC ndc("ShibMySQLCCache::insert");
+  saml::NDC ndc("insert");
 #endif
   
   ostringstream q;
@@ -446,10 +643,13 @@ void ShibMySQLCCache::insert(
     log->error("Error inserting %s: %s", key, err);
     if (isCorrupt(err) && repairTable(mysql,"state")) {
         // Try again...
-        if (mysql_query(mysql, q.str().c_str()))
+        if (mysql_query(mysql, q.str().c_str())) {
           log->error("Error inserting %s: %s", key, mysql_error(mysql));
-          throw SAMLException("ShibMySQLCCache::insert(): inset failed");
+          throw SAMLException("ShibMySQLCCache::insert(): insertion failed");
+        }
     }
+    else
+        throw SAMLException("ShibMySQLCCache::insert(): insertion failed");
   }
 
   // Add it to the memory cache
@@ -459,7 +659,7 @@ void ShibMySQLCCache::insert(
 void ShibMySQLCCache::remove(const char* key)
 {
 #ifdef _DEBUG
-  saml::NDC ndc("ShibMySQLCCache::remove");
+  saml::NDC ndc("remove");
 #endif
 
   // Remove the cached version
@@ -482,11 +682,11 @@ void ShibMySQLCCache::remove(const char* key)
 void ShibMySQLCCache::cleanup()
 {
 #ifdef _DEBUG
-  saml::NDC ndc("ShibMySQLCCache::cleanup");
+  saml::NDC ndc("cleanup");
 #endif
 
   Mutex* mutex = Mutex::create();
-  thread_init();
+  MySQLBase::thread_init();
 
   int rerun_timer = 0;
   int timeout_life = 0;
@@ -560,7 +760,7 @@ void ShibMySQLCCache::cleanup()
 
   mutex->unlock();
   delete mutex;
-  thread_end();
+  MySQLBase::thread_end();
   Thread::exit(NULL);
 }
 
@@ -576,168 +776,6 @@ void* ShibMySQLCCache::cleanup_fcn(void* cache_p)
   return NULL;
 }
 
-bool ShibMySQLCCache::repairTable(MYSQL*& mysql, const char* table)
-{
-  string q = string("REPAIR TABLE ") + table;
-  if (mysql_query(mysql, q.c_str())) {
-    log->error("Error repairing table %s: %s", table, mysql_error(mysql));
-    return false;
-  }
-
-  // seems we have to recycle the connection to get the thread to keep working
-  // other threads seem to be ok, but we should monitor that
-  mysql_close(mysql);
-  m_mysql->setData(NULL);
-  thread_init();
-  mysql=getMYSQL();
-  return true;
-}
-
-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 SAMLException("ShibMySQLCCache::createDatabase(): mysql_init failed");
-    }
-
-    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 SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect failed");
-    }
-
-    if (mysql_query(ms, "CREATE DATABASE shar")) {
-      log->crit("cannot create shar database: %s", mysql_error(ms));
-      throw SAMLException("ShibMySQLCCache::createDatabase(): create db cmd failed");
-    }
-
-    if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
-      log->crit("cannot open SHAR database");
-      throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect to shar db failed");
-    }
-
-    mysql_close(ms);
-    
-  }
-  catch (SAMLException&) {
-    if (ms)
-      mysql_close(ms);
-    mysql_close(mysql);
-    throw;
-  }
-
-  // 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));
-    throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
-  }
-
-  if (mysql_query(mysql,STATE_TABLE)) {
-    log->error ("Error creating state: %s", mysql_error(mysql));
-    throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
-  }
-
-  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));
-    throw SAMLException("ShibMySQLCCache::createDatabase(): version insert failed");
-  }
-}
-
-void ShibMySQLCCache::upgradeDatabase(MYSQL* mysql)
-{
-    if (mysql_query(mysql, "DROP TABLE state")) {
-        log->error("Error dropping old session state table: %s", mysql_error(mysql));
-    }
-
-    if (mysql_query(mysql,STATE_TABLE)) {
-        log->error ("Error creating state table: %s", mysql_error(mysql));
-        throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
-    }
-
-    ostringstream q;
-    q << "UPDATE version SET major = " << PLUGIN_VER_MAJOR;
-    if (mysql_query(mysql, q.str().c_str())) {
-        log->error ("Error updating version: %s", mysql_error(mysql));
-        throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error updating version");
-    }
-}
-
-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 SAMLException("ShibMySQLCCache::getVersion(): version verification failed");
-    }
-  }
-  log->crit("MySQL Read Failed in version verificatoin");
-  throw SAMLException("ShibMySQLCCache::getVersion(): error reading version");
-}
-
-void mysqlInit(const DOMElement* e, Category& log)
-{
-  static bool done = false;
-  if (done) {
-    log.info("MySQL embedded server already initialized");
-    return;
-  }
-  log.info("initializing MySQL embedded server");
-
-  // Setup the argument array
-  vector<string> arg_array;
-  arg_array.push_back("shibboleth");
-
-  // grab any MySQL parameters from the config file
-  e=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,Argument);
-  while (e) {
-      auto_ptr_char arg(e->getFirstChild()->getNodeValue());
-      if (arg.get())
-          arg_array.push_back(arg.get());
-      e=saml::XML::getNextSiblingElement(e,shibtarget::XML::SHIBTARGET_NS,Argument);
-  }
-
-  // Compute the argument array
-  int arg_count = arg_array.size();
-  const char** args=new const char*[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);
-
-  delete[] args;
-  done = true;
-}  
-
 /*************************************************************************
  * The CCacheEntry here is mostly a wrapper around the "memory"
  * cacheentry provided by shibboleth.  The only difference is that we
@@ -760,8 +798,7 @@ bool ShibMySQLCCacheEntry::touch() const
 
   MYSQL* mysql = m_cache->getMYSQL();
   if (mysql_query(mysql, q.c_str())) {
-    m_cache->log->info("Error updating timestamp on %s: %s",
-                       m_key.c_str(), mysql_error(mysql));
+    m_cache->log->info("Error updating timestamp on %s: %s", m_key.c_str(), mysql_error(mysql));
     return false;
   }
   return true;
@@ -834,6 +871,89 @@ ISessionCacheEntry::CachedResponse ShibMySQLCCacheEntry::getResponse()
     return r;
 }
 
+class MySQLReplayCache : public MySQLBase, virtual public IReplayCache
+{
+public:
+  MySQLReplayCache(const DOMElement* e);
+  virtual ~MySQLReplayCache() {}
+
+  void thread_init() {MySQLBase::thread_init();}
+  void thread_end() {MySQLBase::thread_end();}
+
+  bool check(const XMLCh* str, time_t expires) {auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
+  bool check(const char* str, time_t expires);
+};
+
+MySQLReplayCache::MySQLReplayCache(const DOMElement* e) : MySQLBase(e)
+{
+#ifdef _DEBUG
+  saml::NDC ndc("MySQLReplayCache");
+#endif
+
+  log = &(Category::getInstance("shibmysql.ReplayCache"));
+}
+
+bool MySQLReplayCache::check(const char* str, time_t expires)
+{
+#ifdef _DEBUG
+    saml::NDC ndc("check");
+#endif
+  
+    // Remove expired entries
+    string q = string("DELETE FROM replay WHERE expires < NOW()");
+    MYSQL* mysql = getMYSQL();
+    if (mysql_query(mysql, q.c_str())) {
+        const char* err=mysql_error(mysql);
+        log->error("Error deleting expired entries: %s", err);
+        if (isCorrupt(err) && repairTable(mysql,"replay")) {
+            // Try again...
+            if (mysql_query(mysql, q.c_str()))
+                log->error("Error deleting expired entries: %s", mysql_error(mysql));
+        }
+    }
+  
+    string q2 = string("SELECT id FROM replay WHERE id='") + str + "'";
+    if (mysql_query(mysql, q2.c_str())) {
+        const char* err=mysql_error(mysql);
+        log->error("Error searching for %s: %s", str, err);
+        if (isCorrupt(err) && repairTable(mysql,"replay")) {
+            if (mysql_query(mysql, q2.c_str())) {
+                log->error("Error retrying search for %s: %s", str, mysql_error(mysql));
+                throw SAMLException("Replay cache failed, please inform application support staff.");
+            }
+        }
+        else
+            throw SAMLException("Replay cache failed, please inform application support staff.");
+    }
+
+    // Did we find it?
+    MYSQL_RES* rows = mysql_store_result(mysql);
+    if (rows && mysql_num_rows(rows)>0) {
+      mysql_free_result(rows);
+      return false;
+    }
+
+    ostringstream q3;
+    q3 << "INSERT INTO replay VALUES('" << str << "'," << "FROM_UNIXTIME(" << expires << "))";
+
+    // then add it to the database
+    if (mysql_query(mysql, q3.str().c_str())) {
+        const char* err=mysql_error(mysql);
+        log->error("Error inserting %s: %s", str, err);
+        if (isCorrupt(err) && repairTable(mysql,"state")) {
+            // Try again...
+            if (mysql_query(mysql, q3.str().c_str())) {
+                log->error("Error inserting %s: %s", str, mysql_error(mysql));
+                throw SAMLException("Replay cache failed, please inform application support staff.");
+            }
+        }
+        else
+            throw SAMLException("Replay cache failed, please inform application support staff.");
+    }
+    
+    return true;
+}
+
 /*************************************************************************
  * The registration functions here...
  */
@@ -845,7 +965,7 @@ IPlugIn* new_mysql_ccache(const DOMElement* e)
 
 IPlugIn* new_mysql_replay(const DOMElement* e)
 {
-  return NULL;
+  return new MySQLReplayCache(e);
 }
 
 #define REPLAYPLUGINTYPE "edu.internet2.middleware.shibboleth.sp.provider.MySQLReplayCacheProvider"
@@ -861,16 +981,8 @@ extern "C" int SHIBMYSQL_EXPORTS saml_extension_init(void*)
 
 extern "C" void SHIBMYSQL_EXPORTS saml_extension_term()
 {
+  // Shutdown MySQL
+  mysql_server_end();
   SAMLConfig::getConfig().getPlugMgr().unregFactory(REPLAYPLUGINTYPE);
   SAMLConfig::getConfig().getPlugMgr().unregFactory(SESSIONPLUGINTYPE);
 }
-
-/*************************************************************************
- * Local Functions
- */
-
-extern "C" void shib_mysql_destroy_handle(void* data)
-{
-  MYSQL* mysql = (MYSQL*) data;
-  mysql_close(mysql);
-}