Move Shib constants to new lib, fixed symbol conflicts.
[shibboleth/cpp-sp.git] / odbc_ccache / odbc-ccache.cpp
index 1fc7f05..350bb27 100644 (file)
 #ifdef WIN32
 # define _CRT_NONSTDC_NO_DEPRECATE 1
 # define _CRT_SECURE_NO_DEPRECATE 1
+# define NOMINMAX
 # define SHIBODBC_EXPORTS __declspec(dllexport)
 #else
 # define SHIBODBC_EXPORTS
 #endif
 
-#include <shib/shib-threads.h>
 #include <shib-target/shib-target.h>
 #include <log4cpp/Category.hh>
+#include <xmltooling/util/NDC.h>
 
+#include <ctime>
+#include <algorithm>
 #include <sstream>
 
 #include <sql.h>
 #include <dmalloc.h>
 #endif
 
-using namespace std;
-using namespace saml;
-using namespace shibboleth;
 using namespace shibtarget;
+using namespace shibboleth;
+using namespace saml;
+using namespace xmltooling;
 using namespace log4cpp;
+using namespace std;
 
 #define PLUGIN_VER_MAJOR 3
 #define PLUGIN_VER_MINOR 0
@@ -63,7 +67,7 @@ using namespace log4cpp;
 #define COLSIZE_APPLICATION_ID 256
 #define COLSIZE_ADDRESS 128
 #define COLSIZE_PROVIDER_ID 256
-#define LONGDATA_BUFLEN 2048
+#define LONGDATA_BUFLEN 32768
 
 /*
   CREATE TABLE state (
@@ -116,64 +120,59 @@ public:
 
     SQLHDBC getHDBC();
 
-    log4cpp::Category* log;
+    Category* log;
 
 protected:
-    //ThreadKey* m_mysql;
     const DOMElement* m_root; // can only use this during initialization
     string m_connstring;
 
     static SQLHENV m_henv;          // single handle for both plugins
     bool m_bInitializedODBC;        // tracks which class handled the process
+    static const char* p_connstring;
 
     pair<int,int> getVersion(SQLHDBC);
     void log_error(SQLHANDLE handle, SQLSMALLINT htype);
 };
 
 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
-
-/*
-extern "C" void shib_mysql_destroy_handle(void* data)
-{
-  MYSQL* mysql = (MYSQL*) data;
-  if (mysql) mysql_close(mysql);
-}
-*/
+const char* ODBCBase::p_connstring = NULL;
 
 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("ODBCBase");
+    xmltooling::NDC ndc("ODBCBase");
 #endif
-    log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
-    //m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
-
-    if (m_henv != SQL_NULL_HANDLE) {
-        log->info("ODBC already initialized");
-        return;
-    }
+    log = &(Category::getInstance("shibtarget.ODBC"));
 
-    // Enable connection pooling.
-    SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
+    if (m_henv == SQL_NULL_HANDLE) {
+        // Enable connection pooling.
+        SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
 
-    // Allocate the environment.
-    if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
-        throw ConfigurationException("ODBC failed to initialize.");
+        // Allocate the environment.
+        if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
+            throw ConfigurationException("ODBC failed to initialize.");
 
-    // Specify ODBC 3.x
-    SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
+        // Specify ODBC 3.x
+        SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
 
-    log->info("ODBC initialized");
-    m_bInitializedODBC = true;
+        log->info("ODBC initialized");
+        m_bInitializedODBC = true;
+    }
 
     // Grab connection string from the configuration.
     e=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,ConnectionString);
     if (!e || !e->hasChildNodes()) {
-        this->~ODBCBase();
-        throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
+        if (!p_connstring) {
+            this->~ODBCBase();
+            throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
+        }
+        m_connstring=p_connstring;
+    }
+    else {
+        xmltooling::auto_ptr_char arg(e->getFirstChild()->getNodeValue());
+        m_connstring=arg.get();
+        p_connstring=m_connstring.c_str();
     }
-    auto_ptr_char arg(e->getFirstChild()->getNodeValue());
-    m_connstring=arg.get();
 
     // Connect and check version.
     SQLHDBC conn=getHDBC();
@@ -193,7 +192,9 @@ ODBCBase::~ODBCBase()
     //delete m_mysql;
     if (m_bInitializedODBC)
         SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
+    m_bInitializedODBC=false;
     m_henv = SQL_NULL_HANDLE;
+    p_connstring=NULL;
 }
 
 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
@@ -215,7 +216,7 @@ void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
 SQLHDBC ODBCBase::getHDBC()
 {
 #ifdef _DEBUG
-    saml::NDC ndc("getMYSQL");
+    xmltooling::NDC ndc("getMYSQL");
 #endif
 
     // Get a handle.
@@ -256,12 +257,10 @@ pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
     SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
 
     if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return pair<int,int>(major,minor);
     }
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     log->error("no rows returned in version query");
     throw SAMLException("ODBCBase::getVersion failed to read version from database");
@@ -322,9 +321,9 @@ public:
 private:
     bool m_storeAttributes;
     ISessionCache* m_cache;
-    CondWait* shutdown_wait;
+    xmltooling::CondWait* shutdown_wait;
     bool shutdown;
-    Thread* cleanup_thread;
+    xmltooling::Thread* cleanup_thread;
 
     static void* cleanup_fcn(void*); // XXX Assumed an ODBCCCache
 };
@@ -332,8 +331,9 @@ private:
 ODBCCCache::ODBCCCache(const DOMElement* e) : ODBCBase(e), m_storeAttributes(false)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("ODBCCCache");
+    xmltooling::NDC ndc("ODBCCCache");
 #endif
+    log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
 
     m_cache = dynamic_cast<ISessionCache*>(
         SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::MemorySessionCacheType, m_root)
@@ -363,6 +363,18 @@ ODBCCCache::~ODBCCCache()
     delete m_cache;
 }
 
+void appendXML(ostream& os, const char* str)
+{
+    const char* pos=strchr(str,'\'');
+    while (pos) {
+        os.write(str,pos-str);
+        os << "''";
+        str=pos+1;
+        pos=strchr(str,'\'');
+    }
+    os << str;
+}
+
 HRESULT ODBCCCache::onCreate(
     const char* key,
     const IApplication* application,
@@ -373,7 +385,7 @@ HRESULT ODBCCCache::onCreate(
     )
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onCreate");
+    xmltooling::NDC ndc("onCreate");
 #endif
 
     // Get XML data from entry. Default is not to return SAML objects.
@@ -397,8 +409,18 @@ HRESULT ODBCCCache::onCreate(
     ostringstream q;
     q << "INSERT state VALUES ('" << key << "','" << application->getId() << "'," << timebuf << "," << timebuf
         << ",'" << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId()
-        << "',?,?,?)";
-
+        << "','";
+    appendXML(q,subject.first);
+    q << "','";
+    appendXML(q,context);
+    q << "',";
+    if (m_storeAttributes && tokens.first) {
+        q << "'";
+        appendXML(q,tokens.first);
+       q << "')";
+    }
+    else
+        q << "null)";
     if (log->isDebugEnabled())
         log->debug("SQL insert: %s", q.str().c_str());
 
@@ -407,49 +429,15 @@ HRESULT ODBCCCache::onCreate(
     ODBCConn conn(getHDBC());
     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
 
-    // Bind text parameters to statement.
-    SQLINTEGER cbSubject=SQL_LEN_DATA_AT_EXEC(0),cbContext=SQL_LEN_DATA_AT_EXEC(0),cbTokens;
-    if (!m_storeAttributes || !tokens.first)
-        cbTokens=SQL_NULL_DATA;
-    else
-        cbTokens=SQL_LEN_DATA_AT_EXEC(0);
-    SQLRETURN sr=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)subject.first,0,&cbSubject);
-    if (!SQL_SUCCEEDED(sr))
-        log_error(hstmt, SQL_HANDLE_STMT);
-    sr=SQLBindParameter(hstmt,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)context,0,&cbContext);
-    if (!SQL_SUCCEEDED(sr))
-        log_error(hstmt, SQL_HANDLE_STMT);
-    sr=SQLBindParameter(hstmt,3,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)tokens.first,0,&cbTokens);
-    if (!SQL_SUCCEEDED(sr))
-        log_error(hstmt, SQL_HANDLE_STMT);
-
     // Execute statement.
-    sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
-    if (sr==SQL_NEED_DATA) {
-        // Loop to send text data into driver.
-        // pData is set each round by the driver to the pointers we bound above.
-        char* pData;
-        sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
-        while (sr==SQL_NEED_DATA) {
-            size_t len=strlen(pData);
-            while (len>0) {
-                size_t amt = min(LONGDATA_BUFLEN,len);
-                SQLPutData(hstmt, pData, amt);
-                pData += amt;
-                len = len - amt;
-            }
-            sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
-       }
-    }
-
     HRESULT hr=NOERROR;
+    SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
     if (!SQL_SUCCEEDED(sr)) {
         log->error("failed to insert record into database");
         log_error(hstmt, SQL_HANDLE_STMT);
         hr=E_FAIL;
     }
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return hr;
 }
@@ -469,7 +457,7 @@ HRESULT ODBCCCache::onRead(
     )
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onRead");
+    xmltooling::NDC ndc("onRead");
 #endif
 
     log->debug("searching database...");
@@ -483,7 +471,6 @@ HRESULT ODBCCCache::onRead(
     if (!SQL_SUCCEEDED(sr)) {
         log->error("error searching for (%s)",key);
         log_error(hstmt, SQL_HANDLE_STMT);
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return E_FAIL;
     }
@@ -504,7 +491,6 @@ HRESULT ODBCCCache::onRead(
     SQLBindCol(hstmt,7,SQL_C_CHAR,provider_id,sizeof(provider_id),NULL);
 
     if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return S_FALSE;
     }
@@ -563,7 +549,6 @@ HRESULT ODBCCCache::onRead(
         }
     }
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return hr;
 }
@@ -571,7 +556,7 @@ HRESULT ODBCCCache::onRead(
 HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onRead");
+    xmltooling::NDC ndc("onRead");
 #endif
 
     log->debug("reading last access time from database");
@@ -585,7 +570,6 @@ HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
     if (!SQL_SUCCEEDED(sr)) {
         log->error("error searching for (%s)",key);
         log_error(hstmt, SQL_HANDLE_STMT);
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return E_FAIL;
     }
@@ -595,12 +579,10 @@ HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
 
     if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
         log->warn("session expected, but not found in database");
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return S_FALSE;
     }
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
 
     struct tm t;
@@ -623,7 +605,7 @@ HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
 HRESULT ODBCCCache::onRead(const char* key, string& tokens)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onRead");
+    xmltooling::NDC ndc("onRead");
 #endif
 
     if (!m_storeAttributes)
@@ -640,14 +622,12 @@ HRESULT ODBCCCache::onRead(const char* key, string& tokens)
     if (!SQL_SUCCEEDED(sr)) {
         log->error("error searching for (%s)",key);
         log_error(hstmt, SQL_HANDLE_STMT);
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return E_FAIL;
     }
 
     if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
         log->warn("session expected, but not found in database");
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
         return S_FALSE;
     }
@@ -665,7 +645,6 @@ HRESULT ODBCCCache::onRead(const char* key, string& tokens)
         tokens += (char*)buf;
     }
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return hr;
 }
@@ -673,12 +652,10 @@ HRESULT ODBCCCache::onRead(const char* key, string& tokens)
 HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onUpdate");
+    xmltooling::NDC ndc("onUpdate");
 #endif
 
-    SQLRETURN sr;
-    SQLHSTMT hstmt;
-    ODBCConn conn(getHDBC());
+    ostringstream q;
 
     if (lastAccess>0) {
 #ifndef HAVE_GMTIME_R
@@ -690,48 +667,31 @@ HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAcc
         char timebuf[32];
         strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
 
-        ostringstream q;
         q << "UPDATE state SET atime=" << timebuf << " WHERE cookie='" << key << "'";
-
-        SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
-        sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
     }
     else if (tokens) {
         if (!m_storeAttributes)
             return S_FALSE;
-        string q = string("UPDATE state SET tokens=? WHERE cookie='") + key + "'";
-
-        SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
-
-        // Bind text parameters to statement.
-        SQLINTEGER cbTokens = tokens ? SQL_LEN_DATA_AT_EXEC(0) : SQL_NULL_DATA;
-        sr=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)tokens,0,&cbTokens);
-
-        // Execute statement.
-        sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
-        if (sr==SQL_NEED_DATA) {
-            // Loop to send text data into driver.
-            // pData is set each round by the driver to the pointers we bound above.
-            char* pData;
-            sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
-            while (sr==SQL_NEED_DATA) {
-                size_t len=strlen(pData);
-                while (len>0) {
-                    size_t amt=min(LONGDATA_BUFLEN,len);
-                    SQLPutData(hstmt, pData, amt);
-                    pData += amt;
-                    len = len - amt;
-                }
-                sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
-           }
-        }
+        q << "UPDATE state SET tokens=";
+       if (*tokens) {
+           q << "'";
+           appendXML(q,tokens);
+           q << "' ";
+       }
+       else
+           q << "null ";
+       q << "WHERE cookie='" << key << "'";
     }
     else {
         log->warn("onUpdate called with nothing to do!");
         return S_FALSE;
     }
  
-    HRESULT hr;
+    HRESULT hr=NOERROR;
+    SQLHSTMT hstmt;
+    ODBCConn conn(getHDBC());
+    SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
+    SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
     if (sr==SQL_NO_DATA)
         hr=S_FALSE;
     else if (!SQL_SUCCEEDED(sr)) {
@@ -739,10 +699,7 @@ HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAcc
         log_error(hstmt, SQL_HANDLE_STMT);
         hr=E_FAIL;
     }
-    else
-        hr=NOERROR;
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return hr;
 }
@@ -750,7 +707,7 @@ HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAcc
 HRESULT ODBCCCache::onDelete(const char* key)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("onDelete");
+    xmltooling::NDC ndc("onDelete");
 #endif
 
     SQLHSTMT hstmt;
@@ -759,7 +716,7 @@ HRESULT ODBCCCache::onDelete(const char* key)
     string q = string("DELETE FROM state WHERE cookie='") + key + "'";
     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
  
-    HRESULT hr;
+    HRESULT hr=NOERROR;
     if (sr==SQL_NO_DATA)
         hr=S_FALSE;
     else if (!SQL_SUCCEEDED(sr)) {
@@ -767,10 +724,7 @@ HRESULT ODBCCCache::onDelete(const char* key)
         log_error(hstmt, SQL_HANDLE_STMT);
         hr=E_FAIL;
     }
-    else
-        hr=NOERROR;
 
-    SQLCloseCursor(hstmt);
     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return hr;
 }
@@ -778,10 +732,10 @@ HRESULT ODBCCCache::onDelete(const char* key)
 void ODBCCCache::cleanup()
 {
 #ifdef _DEBUG
-    saml::NDC ndc("cleanup");
+    xmltooling::NDC ndc("cleanup");
 #endif
 
-    Mutex* mutex = Mutex::create();
+    Mutex* mutex = xmltooling::Mutex::create();
 
     int rerun_timer = 0;
     int timeout_life = 0;
@@ -847,7 +801,6 @@ void ODBCCCache::cleanup()
         if (SQL_SUCCEEDED(sr) && rowcount > 0)
             log->info("purging %d old sessions",rowcount);
 
-        SQLCloseCursor(hstmt);
         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
      }
 
@@ -855,122 +808,126 @@ void ODBCCCache::cleanup()
 
     mutex->unlock();
     delete mutex;
-    Thread::exit(NULL);
+    xmltooling::Thread::exit(NULL);
 }
 
 void* ODBCCCache::cleanup_fcn(void* cache_p)
 {
   ODBCCCache* cache = (ODBCCCache*)cache_p;
 
+#ifndef WIN32
   // First, let's block all signals
   Thread::mask_all_signals();
+#endif
 
   // Now run the cleanup process.
   cache->cleanup();
   return NULL;
 }
 
-/*
-class MySQLReplayCache : public MySQLBase, virtual public IReplayCache
+
+class ODBCReplayCache : public ODBCBase, virtual public IReplayCache
 {
 public:
-  MySQLReplayCache(const DOMElement* e);
-  virtual ~MySQLReplayCache() {}
+  ODBCReplayCache(const DOMElement* e);
+  virtual ~ODBCReplayCache() {}
 
-  bool check(const XMLCh* str, time_t expires) {auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
+  bool check(const XMLCh* str, time_t expires) {xmltooling::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)
+ODBCReplayCache::ODBCReplayCache(const DOMElement* e) : ODBCBase(e)
 {
 #ifdef _DEBUG
-  saml::NDC ndc("MySQLReplayCache");
+    saml::NDC ndc("ODBCReplayCache");
 #endif
-
-  log = &(Category::getInstance("shibmysql.ReplayCache"));
+    log = &(Category::getInstance("shibtarget.ReplayCache.ODBC"));
 }
 
-bool MySQLReplayCache::check(const char* str, time_t expires)
+bool ODBCReplayCache::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));
-        }
+    time_t now=time(NULL);
+#ifndef HAVE_GMTIME_R
+    struct tm* ptime=gmtime(&now);
+#else
+    struct tm res;
+    struct tm* ptime=gmtime_r(&now,&res);
+#endif
+    char timebuf[32];
+    strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
+
+    // Remove expired entries.
+    SQLHSTMT hstmt;
+    ODBCConn conn(getHDBC());
+    SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
+    string q = string("DELETE FROM replay WHERE expires < ") + timebuf;
+    SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
+    if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
+        log->error("error purging old replay cache entries");
+        log_error(hstmt, SQL_HANDLE_STMT);
     }
+    SQLCloseCursor(hstmt);
   
-    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.");
+    // Look for a replay.
+    q = string("SELECT id FROM replay WHERE id='") + str + "'";
+    sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
+    if (!SQL_SUCCEEDED(sr)) {
+        log->error("error searching replay cache");
+        log_error(hstmt, SQL_HANDLE_STMT);
+        SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
+        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;
+    // If we got a record, we return false.
+    if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
+        SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
+        return false;
     }
+    SQLCloseCursor(hstmt);
+    
+#ifndef HAVE_GMTIME_R
+    ptime=gmtime(&expires);
+#else
+    ptime=gmtime_r(&expires,&res);
+#endif
+    strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
 
-    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.");
+    // Add it to the database.
+    q = string("INSERT replay VALUES('") + str + "'," + timebuf + ")";
+    sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
+    if (!SQL_SUCCEEDED(sr)) {
+        log->error("error inserting replay cache entry", str);
+        log_error(hstmt, SQL_HANDLE_STMT);
+        SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
+        throw SAMLException("Replay cache failed, please inform application support staff.");
     }
-    
+
+    SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
     return true;
 }
 
-IPlugIn* new_mysql_replay(const DOMElement* e)
-{
-    return new MySQLReplayCache(e);
-}
-*/
 
-/*************************************************************************
- * The registration functions here...
- */
+// Factories
 
 IPlugIn* new_odbc_ccache(const DOMElement* e)
 {
     return new ODBCCCache(e);
 }
 
+IPlugIn* new_odbc_replay(const DOMElement* e)
+{
+    return new ODBCReplayCache(e);
+}
+
 
 extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
 {
     // register this ccache type
-//    SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCReplayCacheType, &new_odbc_replay);
+    SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCReplayCacheType, &new_odbc_replay);
     SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCSessionCacheType, &new_odbc_ccache);
     return 0;
 }
@@ -978,5 +935,5 @@ extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
 extern "C" void SHIBODBC_EXPORTS saml_extension_term()
 {
     SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCSessionCacheType);
-//    SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCReplayCacheType);
+    SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCReplayCacheType);
 }