/*
- * shib-mysql-ccache.cpp: Shibboleth Credential Cache using MySQL.
+ * Copyright 2001-2005 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * Created by: Derek Atkins <derek@ihtfp.com>
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * $Id$
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-/* 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.
+/*
+ * shib-mysql-ccache.cpp: Shibboleth Credential Cache using MySQL.
+ *
+ * Created by: Derek Atkins <derek@ihtfp.com>
*
- * 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.
+ * $Id$
*/
// eventually we might be able to support autoconf via cygwin...
#endif
#include <shib-target/shib-target.h>
-#include <shib/shib-threads.h>
+
+#include <xmltooling/util/NDC.h>
#include <log4cpp/Category.hh>
#include <sstream>
-#include <stdexcept>
+#ifdef WIN32
+# include <winsock.h>
+#endif
#include <mysql.h>
// wanted to use MySQL codes for this, but can't seem to get back a 145
#include <dmalloc.h>
#endif
-using namespace std;
-using namespace saml;
-using namespace shibboleth;
using namespace shibtarget;
+using namespace opensaml::saml2md;
+using namespace saml;
+using namespace log4cpp;
+using namespace std;
-#define PLUGIN_VER_MAJOR 1
+#define PLUGIN_VER_MAJOR 3
#define PLUGIN_VER_MINOR 0
+#define STATE_TABLE \
+ "CREATE TABLE state (" \
+ "cookie VARCHAR(64) PRIMARY KEY, " \
+ "application_id VARCHAR(255)," \
+ "ctime TIMESTAMP," \
+ "atime TIMESTAMP," \
+ "addr VARCHAR(128)," \
+ "major INT," \
+ "minor INT," \
+ "provider VARCHAR(256)," \
+ "subject TEXT," \
+ "authn_context TEXT," \
+ "tokens 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[] =
{ chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
static const XMLCh mysqlTimeout[] =
{ chLatin_m, chLatin_y, chLatin_s, chLatin_q, chLatin_l, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
+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 };
+
+static bool g_MySQLInitialized = false;
-class ShibMySQLCCache;
-class ShibMySQLCCacheEntry : public ISessionCacheEntry
+class MySQLBase : public virtual saml::IPlugIn
{
public:
- ShibMySQLCCacheEntry(const char*, ISessionCacheEntry*, ShibMySQLCCache*);
- ~ShibMySQLCCacheEntry() {}
+ MySQLBase(const DOMElement* e);
+ virtual ~MySQLBase();
- 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 const SAMLAuthenticationStatement* getAuthnStatement() const { return m_cacheEntry->getAuthnStatement(); }
- virtual Iterator<SAMLAssertion*> getAssertions() { return m_cacheEntry->getAssertions(); }
+ MYSQL* getMYSQL();
+ bool repairTable(MYSQL*&, const char* table);
-private:
- bool touch() const;
+ log4cpp::Category* log;
+
+protected:
+ xmltooling::ThreadKey* m_mysql;
+ const DOMElement* m_root; // can only use this during initialization
+
+ bool initialized;
+ bool handleShutdown;
- ShibMySQLCCache* m_cache;
- ISessionCacheEntry* m_cacheEntry;
- string m_key;
+ void createDatabase(MYSQL*, int major, int minor);
+ void upgradeDatabase(MYSQL*);
+ pair<int,int> getVersion(MYSQL*);
};
-class ShibMySQLCCache : public ISessionCache
+// Forward declarations
+static void mysqlInit(const DOMElement* e, Category& log);
+
+extern "C" void shib_mysql_destroy_handle(void* data)
{
-public:
- ShibMySQLCCache(const DOMElement* e);
- virtual ~ShibMySQLCCache();
+ MYSQL* mysql = (MYSQL*) data;
+ if (mysql) mysql_close(mysql);
+}
- virtual void thread_init();
- virtual void thread_end() {}
+MySQLBase::MySQLBase(const DOMElement* e) : m_root(e)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("MySQLBase");
+#endif
+ log = &(Category::getInstance("shibtarget.SessionCache.MySQL"));
- 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,
- SAMLAuthenticationStatement *s,
- const char *client_addr,
- SAMLResponse* r=NULL,
- const IRoleDescriptor* source=NULL);
- virtual void remove(const char* key);
+ m_mysql = xmltooling::ThreadKey::create(&shib_mysql_destroy_handle);
- void cleanup();
- MYSQL* getMYSQL() const;
+ initialized = false;
+ mysqlInit(e,*log);
+ getMYSQL();
+ initialized = true;
+}
- log4cpp::Category* log;
+MySQLBase::~MySQLBase()
+{
+ delete m_mysql;
+}
-private:
- ISessionCache* m_cache;
- ThreadKey* m_mysql;
- const DOMElement* m_root; // can only use this during initialization
+MYSQL* MySQLBase::getMYSQL()
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("getMYSQL");
+#endif
- static void* cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
- CondWait* shutdown_wait;
- bool shutdown;
- Thread* cleanup_thread;
+ // Do we already have a handle?
+ MYSQL* mysql=reinterpret_cast<MYSQL*>(m_mysql->getData());
+ if (mysql)
+ return mysql;
- bool initialized;
+ // Connect to the database
+ mysql = mysql_init(NULL);
+ if (!mysql) {
+ log->error("mysql_init failed");
+ mysql_close(mysql);
+ throw SAMLException("MySQLBase::getMYSQL(): mysql_init() failed");
+ }
- void createDatabase(MYSQL*, int major, int minor);
- void upgradeDatabase(MYSQL*);
- void getVersion(MYSQL*, int* major_p, int* minor_p);
- bool repairTable(MYSQL*&, const char* table);
- void mysqlInit(void);
-};
+ if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shibd", 0, NULL, 0)) {
+ if (initialized) {
+ log->crit("mysql_real_connect failed: %s", mysql_error(mysql));
+ mysql_close(mysql);
+ throw SAMLException("MySQLBase::getMYSQL(): mysql_real_connect() failed");
+ }
+ else {
+ log->info("mysql_real_connect failed: %s. Trying to create", mysql_error(mysql));
-// Forward declarations
-extern "C" void shib_mysql_destroy_handle(void* data);
+ // This will throw an exception if it fails.
+ createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
+ }
+ }
-/*************************************************************************
- * 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.
- */
+ pair<int,int> v=getVersion (mysql);
-MYSQL* ShibMySQLCCache::getMYSQL() const
+ // Make sure we've got the right version
+ if (v.first != PLUGIN_VER_MAJOR || v.second != PLUGIN_VER_MINOR) {
+
+ // If we're capable, try upgrading on the fly...
+ if (v.first == 0 || v.first == 1 || v.first == 2) {
+ if (mysql_query(mysql, "DROP TABLE state")) {
+ log->error("error dropping old session state table: %s", mysql_error(mysql));
+ }
+ if (v.first==2 && mysql_query(mysql, "DROP TABLE replay")) {
+ log->error("error dropping old session state table: %s", mysql_error(mysql));
+ }
+ upgradeDatabase(mysql);
+ }
+ else {
+ mysql_close(mysql);
+ log->crit("Unknown database version: %d.%d", v.first, v.second);
+ throw SAMLException("MySQLBase::getMYSQL(): Unknown database version");
+ }
+ }
+
+ // We're all set.. Save off the handle for this thread.
+ m_mysql->setData(mysql);
+ return mysql;
+}
+
+bool MySQLBase::repairTable(MYSQL*& mysql, const char* table)
{
- return (MYSQL*)m_mysql->getData();
+ 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);
+ mysql=getMYSQL();
+ return true;
}
-void ShibMySQLCCache::thread_init()
+void MySQLBase::createDatabase(MYSQL* mysql, int major, int minor)
{
- saml::NDC ndc("thread_init");
+ 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");
+ }
- // Connect to the database
- MYSQL* mysql = mysql_init(NULL);
- if (!mysql) {
- log->error("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 shibd")) {
+ log->crit("cannot create shibd database: %s", mysql_error(ms));
+ throw SAMLException("ShibMySQLCCache::createDatabase(): create db cmd failed");
+ }
+
+ if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shibd", 0, NULL, 0)) {
+ log->crit("cannot open shibd 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 SAMLException("ShibMySQLCCache::thread_init(): mysql_init() failed");
+ throw;
}
- 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");
- } else {
- log->info("mysql_real_connect failed: %s. Trying to create", mysql_error(mysql));
+ // Now create the tables if they don't exist
+ log->info("Creating database tables");
- // This will throw an exception if it fails.
- createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
- }
+ 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");
}
- int major = -1, minor = -1;
- getVersion (mysql, &major, &minor);
+ if (mysql_query(mysql,STATE_TABLE)) {
+ log->error ("error creating state table: %s", mysql_error(mysql));
+ throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
+ }
- // 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 && minor == 0) {
- upgradeDatabase(mysql);
+ 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,STATE_TABLE)) {
+ log->error ("error creating state table: %s", mysql_error(mysql));
+ throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
}
- else {
- mysql_close(mysql);
- log->crit("Invalid database version: %d.%d", major, minor);
- throw SAMLException("ShibMySQLCCache::thread_init(): Invalid database version");
+
+ if (mysql_query(mysql,REPLAY_TABLE)) {
+ log->error ("error creating replay table: %s", mysql_error(mysql));
+ throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating replay table");
}
- }
- // We're all set.. Save off the handle for this thread.
- m_mysql->setData(mysql);
+ 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");
+ }
}
-ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e)
+pair<int,int> MySQLBase::getVersion(MYSQL* mysql)
{
- saml::NDC ndc("shibmysql::ShibMySQLCCache");
+ // grab the version number from the database
+ if (mysql_query(mysql, "SELECT * FROM version")) {
+ log->error("error reading version: %s", mysql_error(mysql));
+ throw SAMLException("MySQLBase::getVersion(): error reading version");
+ }
- m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
- log = &(log4cpp::Category::getInstance("shibmysql::ShibMySQLCCache"));
+ 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);
+ return make_pair(major,minor);
+ }
+ 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("MySQLBase::getVersion(): version verification failed");
+ }
+ }
+ log->crit("MySQL Read Failed in version verification");
+ throw SAMLException("MySQLBase::getVersion(): error reading version");
+}
- m_root=e;
- initialized = false;
- mysqlInit();
- thread_init();
- initialized = true;
+static void mysqlInit(const DOMElement* e, Category& log)
+{
+ if (g_MySQLInitialized) {
+ 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
+ vector<string>::size_type arg_count = arg_array.size();
+ const char** args=new const char*[arg_count];
+ for (vector<string>::size_type 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;
+ g_MySQLInitialized = true;
+}
+
+class ShibMySQLCCache : public MySQLBase, virtual public ISessionCache, virtual public ISessionCacheStore
+{
+public:
+ ShibMySQLCCache(const DOMElement* e);
+ virtual ~ShibMySQLCCache();
- m_cache = dynamic_cast<ISessionCache*>(
- SAMLConfig::getConfig().getPlugMgr().newPlugin(
- "edu.internet2.middleware.shibboleth.target.provider.MemorySessionCache", e
+ // Delegate all the ISessionCache methods.
+ string insert(
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
)
+ { return m_cache->insert(application,role,client_addr,subject,authnContext,tokens); }
+ ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
+ { return m_cache->find(key,application,client_addr); }
+ void remove(const char* key, const IApplication* application, const char* client_addr)
+ { m_cache->remove(key,application,client_addr); }
+
+ bool setBackingStore(ISessionCacheStore*) {return false;}
+
+ // Store methods handle the database work
+ HRESULT onCreate(
+ const char* key,
+ const IApplication* application,
+ const ISessionCacheEntry* entry,
+ int majorVersion,
+ int minorVersion,
+ time_t created
+ );
+ HRESULT onRead(
+ const char* key,
+ string& applicationId,
+ string& clientAddress,
+ string& providerId,
+ string& subject,
+ string& authnContext,
+ string& tokens,
+ int& majorVersion,
+ int& minorVersion,
+ time_t& created,
+ time_t& accessed
+ );
+ HRESULT onRead(const char* key, time_t& accessed);
+ HRESULT onRead(const char* key, string& tokens);
+ HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
+ HRESULT onDelete(const char* key);
+
+ void cleanup();
+
+private:
+ bool m_storeAttributes;
+ ISessionCache* m_cache;
+ xmltooling::CondWait* shutdown_wait;
+ bool shutdown;
+ xmltooling::Thread* cleanup_thread;
+
+ static void* cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
+};
+
+ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e) : MySQLBase(e), m_storeAttributes(false)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("ShibMySQLCCache");
+#endif
+
+ m_cache = dynamic_cast<ISessionCache*>(
+ SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::MemorySessionCacheType, e)
);
+ if (!m_cache->setBackingStore(this)) {
+ delete m_cache;
+ throw SAMLException("Unable to register MySQL cache plugin as a cache store.");
+ }
+
+ shutdown_wait = xmltooling::CondWait::create();
+ shutdown = false;
+
+ // Load our configuration details...
+ const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
+ if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
+ m_storeAttributes=true;
- // Initialize the cleanup thread
- shutdown_wait = CondWait::create();
- shutdown = false;
- cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
+ // Initialize the cleanup thread
+ cleanup_thread = xmltooling::Thread::create(&cleanup_fcn, (void*)this);
}
ShibMySQLCCache::~ShibMySQLCCache()
{
- shutdown = true;
- shutdown_wait->signal();
- cleanup_thread->join(NULL);
+ shutdown = true;
+ shutdown_wait->signal();
+ cleanup_thread->join(NULL);
+ delete m_cache;
+}
- thread_end();
- delete m_cache;
- delete m_mysql;
+HRESULT ShibMySQLCCache::onCreate(
+ const char* key,
+ const IApplication* application,
+ const ISessionCacheEntry* entry,
+ int majorVersion,
+ int minorVersion,
+ time_t created
+ )
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onCreate");
+#endif
+
+ // Get XML data from entry. Default is not to return SAML objects.
+ const char* context=entry->getAuthnContext();
+ pair<const char*,const SAMLSubject*> subject=entry->getSubject();
+ pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
+
+ ostringstream q;
+ q << "INSERT INTO state VALUES('" << key << "','" << application->getId() << "',";
+ if (created==0)
+ q << "NOW(),NOW(),'";
+ else
+ q << "FROM_UNIXTIME(" << created << "),NOW(),'";
+ q << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId() << "','"
+ << subject.first << "','" << context << "',";
+
+ if (m_storeAttributes && tokens.first)
+ q << "'" << tokens.first << "')";
+ else
+ q << "null)";
+
+ if (log->isDebugEnabled())
+ log->debug("SQL insert: %s", q.str().c_str());
+
+ // then add it to the database
+ MYSQL* mysql = getMYSQL();
+ if (mysql_query(mysql, q.str().c_str())) {
+ const char* err=mysql_error(mysql);
+ log->error("error inserting %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ // Try again...
+ if (mysql_query(mysql, q.str().c_str())) {
+ log->error("error inserting %s: %s", key, mysql_error(mysql));
+ return E_FAIL;
+ }
+ }
+ else
+ throw E_FAIL;
+ }
- // Shutdown MySQL
- mysql_server_end();
+ return NOERROR;
}
-ISessionCacheEntry* ShibMySQLCCache::find(const char* key, const IApplication* application)
+HRESULT ShibMySQLCCache::onRead(
+ const char* key,
+ string& applicationId,
+ string& clientAddress,
+ string& providerId,
+ string& subject,
+ string& authnContext,
+ string& tokens,
+ int& majorVersion,
+ int& minorVersion,
+ time_t& created,
+ time_t& accessed
+ )
{
- saml::NDC ndc("ShibMySQLCCache::find");
- ISessionCacheEntry* res = m_cache->find(key, application);
- if (!res) {
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onRead");
+#endif
- log->debug("Looking in database...");
+ log->debug("searching MySQL database...");
- // nothing cached; see if this exists in the database
- string q = string("SELECT application_id,addr,statement FROM state WHERE cookie='") + key + "' LIMIT 1";
+ string q = string("SELECT application_id,UNIX_TIMESTAMP(ctime),UNIX_TIMESTAMP(atime),addr,major,minor,provider,subject,authn_context,tokens FROM state WHERE cookie='") + key + "' LIMIT 1";
- MYSQL_RES* rows;
MYSQL* mysql = getMYSQL();
if (mysql_query(mysql, q.c_str())) {
- const char* err=mysql_error(mysql);
- log->error("Error searching for %s: %s", key, err);
- if (isCorrupt(err) && repairTable(mysql,"state")) {
- if (mysql_query(mysql, q.c_str()))
- log->error("Error retrying search for %s: %s", key, mysql_error(mysql));
- }
+ const char* err=mysql_error(mysql);
+ log->error("error searching for %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ if (mysql_query(mysql, q.c_str()))
+ log->error("error retrying search for %s: %s", key, mysql_error(mysql));
+ }
}
- rows = mysql_store_result(mysql);
+ MYSQL_RES* rows = mysql_store_result(mysql);
// Nope, doesn't exist.
- if (!rows)
- return NULL;
+ if (!rows || mysql_num_rows(rows)==0) {
+ log->debug("not found in database");
+ if (rows)
+ mysql_free_result(rows);
+ return S_FALSE;
+ }
- // 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;
+ // Make sure we got 1 and only 1 row.
+ if (mysql_num_rows(rows) > 1) {
+ log->error("database select returned %d rows!", mysql_num_rows(rows));
+ mysql_free_result(rows);
+ return E_FAIL;
}
- log->debug("Match found. Parsing...");
+ log->debug("session found, tranfering data back into memory");
+
+ /* Columns in query:
+ 0: application_id
+ 1: ctime
+ 2: atime
+ 3: address
+ 4: major
+ 5: minor
+ 6: provider
+ 7: subject
+ 8: authncontext
+ 9: tokens
+ */
- // Pull apart the row and process the results
MYSQL_ROW row = mysql_fetch_row(rows);
- IConfig* conf=ShibTargetConfig::getConfig().getINI();
- Locker locker(conf);
- const IApplication* application=conf->getApplication(row[0]);
- if (!application) {
- mysql_free_result(rows);
- throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"unable to locate application for session, deleted?");
- }
- else if (strcmp(row[0],application->getId())) {
- log->crit("An application (%s) attempted to access another application's (%s) session!", application->getId(), row[0]);
- mysql_free_result(rows);
- return NULL;
+ applicationId=row[0];
+ created=atoi(row[1]);
+ accessed=atoi(row[2]);
+ clientAddress=row[3];
+ majorVersion=atoi(row[4]);
+ minorVersion=atoi(row[5]);
+ providerId=row[6];
+ subject=row[7];
+ authnContext=row[8];
+ if (row[9])
+ tokens=row[9];
+
+ // Free the results.
+ mysql_free_result(rows);
+
+ return NOERROR;
+}
+
+HRESULT ShibMySQLCCache::onRead(const char* key, time_t& accessed)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onRead");
+#endif
+
+ log->debug("reading last access time from MySQL database");
+
+ string q = string("SELECT UNIX_TIMESTAMP(atime) FROM state WHERE cookie='") + key + "' LIMIT 1";
+
+ MYSQL* mysql = getMYSQL();
+ if (mysql_query(mysql, q.c_str())) {
+ const char* err=mysql_error(mysql);
+ log->error("error searching for %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ if (mysql_query(mysql, q.c_str()))
+ log->error("error retrying search for %s: %s", key, mysql_error(mysql));
+ }
}
- istringstream str(row[2]);
- SAMLAuthenticationStatement *s = NULL;
+ MYSQL_RES* rows = mysql_store_result(mysql);
- // Try to parse the AuthStatement
- try {
- s = new SAMLAuthenticationStatement(str);
- } catch (...) {
- mysql_free_result(rows);
- throw;
+ // Nope, doesn't exist.
+ if (!rows || mysql_num_rows(rows)==0) {
+ log->warn("session expected, but not found in database");
+ if (rows)
+ mysql_free_result(rows);
+ return S_FALSE;
}
- // Insert it into the memory cache
- if (s)
- m_cache->insert(key, application, s, row[1]);
+ // Make sure we got 1 and only 1 row.
+ if (mysql_num_rows(rows) != 1) {
+ log->error("database select returned %d rows!", mysql_num_rows(rows));
+ mysql_free_result(rows);
+ return E_FAIL;
+ }
- // Free the results, and then re-run the 'find' query
+ MYSQL_ROW row = mysql_fetch_row(rows);
+ accessed=atoi(row[0]);
+
+ // Free the results.
mysql_free_result(rows);
- res = m_cache->find(key,application);
- if (!res)
- return NULL;
- }
- return new ShibMySQLCCacheEntry(key, res, this);
+ return NOERROR;
}
-void ShibMySQLCCache::insert(
- const char* key,
- const IApplication* application,
- saml::SAMLAuthenticationStatement *s,
- const char *client_addr,
- saml::SAMLResponse* r,
- const IRoleDescriptor* source)
+HRESULT ShibMySQLCCache::onRead(const char* key, string& tokens)
{
- saml::NDC ndc("ShibMySQLCCache::insert");
- ostringstream os;
- os << *s;
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onRead");
+#endif
- string q = string("INSERT INTO state VALUES('") + key + "','" + application->getId() + "',NOW(),'" + client_addr + "','" + os.str() + "')";
+ if (!m_storeAttributes)
+ return S_FALSE;
- log->debug("Query: %s", q.c_str());
+ log->debug("reading cached tokens from MySQL database");
- // then add it to the database
- MYSQL* mysql = getMYSQL();
- if (mysql_query(mysql, q.c_str())) {
- const char* err=mysql_error(mysql);
- log->error("Error inserting %s: %s", key, err);
- if (isCorrupt(err) && repairTable(mysql,"state")) {
- // Try again...
- if (mysql_query(mysql, q.c_str()))
- log->error("Error inserting %s: %s", key, mysql_error(mysql));
- throw SAMLException("ShibMySQLCCache::insert(): inset failed");
+ string q = string("SELECT tokens FROM state WHERE cookie='") + key + "' LIMIT 1";
+
+ MYSQL* mysql = getMYSQL();
+ if (mysql_query(mysql, q.c_str())) {
+ const char* err=mysql_error(mysql);
+ log->error("error searching for %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ if (mysql_query(mysql, q.c_str()))
+ log->error("error retrying search for %s: %s", key, mysql_error(mysql));
+ }
}
- }
- // Add it to the memory cache
- m_cache->insert(key, application, s, client_addr, r, source);
+ MYSQL_RES* rows = mysql_store_result(mysql);
+
+ // Nope, doesn't exist.
+ if (!rows || mysql_num_rows(rows)==0) {
+ log->warn("session expected, but not found in database");
+ if (rows)
+ mysql_free_result(rows);
+ return S_FALSE;
+ }
+
+ // Make sure we got 1 and only 1 row.
+ if (mysql_num_rows(rows) != 1) {
+ log->error("database select returned %d rows!", mysql_num_rows(rows));
+ mysql_free_result(rows);
+ return E_FAIL;
+ }
+
+ MYSQL_ROW row = mysql_fetch_row(rows);
+ if (row[0])
+ tokens=row[0];
+
+ // Free the results.
+ mysql_free_result(rows);
+
+ return NOERROR;
}
-void ShibMySQLCCache::remove(const char* key)
+HRESULT ShibMySQLCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
{
- saml::NDC ndc("ShibMySQLCCache::remove");
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onUpdate");
+#endif
- // Remove the cached version
- m_cache->remove(key);
+ ostringstream q;
+ if (lastAccess>0)
+ q << "UPDATE state SET atime=FROM_UNIXTIME(" << lastAccess << ")";
+ else if (tokens) {
+ if (!m_storeAttributes)
+ return S_FALSE;
+ q << "UPDATE state SET tokens=";
+ if (*tokens)
+ q << "'" << tokens << "'";
+ else
+ q << "null";
+ }
+ else {
+ log->warn("onUpdate called with nothing to do!");
+ return S_FALSE;
+ }
+
+ q << " WHERE cookie='" << key << "'";
- // Remove from the database
- string q = string("DELETE FROM state WHERE cookie='") + key + "'";
- MYSQL* mysql = getMYSQL();
- if (mysql_query(mysql, q.c_str())) {
- const char* err=mysql_error(mysql);
- log->error("Error deleting entry %s: %s", key, err);
- if (isCorrupt(err) && repairTable(mysql,"state")) {
- // Try again...
- if (mysql_query(mysql, q.c_str()))
- log->error("Error deleting entry %s: %s", key, mysql_error(mysql));
+ MYSQL* mysql = getMYSQL();
+ if (mysql_query(mysql, q.str().c_str())) {
+ const char* err=mysql_error(mysql);
+ log->error("error updating %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ // Try again...
+ if (mysql_query(mysql, q.str().c_str())) {
+ log->error("error updating %s: %s", key, mysql_error(mysql));
+ return E_FAIL;
+ }
+ }
+ else
+ return E_FAIL;
}
- }
+
+ return NOERROR;
+}
+
+HRESULT ShibMySQLCCache::onDelete(const char* key)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("onDelete");
+#endif
+
+ // Remove from the database
+ string q = string("DELETE FROM state WHERE cookie='") + key + "'";
+ MYSQL* mysql = getMYSQL();
+ if (mysql_query(mysql, q.c_str())) {
+ const char* err=mysql_error(mysql);
+ log->error("error deleting entry %s: %s", key, err);
+ if (isCorrupt(err) && repairTable(mysql,"state")) {
+ // Try again...
+ if (mysql_query(mysql, q.c_str())) {
+ log->error("error deleting entry %s: %s", key, mysql_error(mysql));
+ return E_FAIL;
+ }
+ }
+ else
+ return E_FAIL;
+ }
+
+ return NOERROR;
}
void ShibMySQLCCache::cleanup()
{
- Mutex* mutex = Mutex::create();
- saml::NDC ndc("ShibMySQLCCache::cleanup");
+#ifdef _DEBUG
+ xmltooling::NDC ndc("cleanup");
+#endif
- thread_init();
+ xmltooling::Mutex* mutex = xmltooling::Mutex::create();
int rerun_timer = 0;
int timeout_life = 0;
MYSQL* mysql = getMYSQL();
+ log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
+
while (shutdown == false) {
shutdown_wait->timedwait(mutex, rerun_timer);
// 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;
+ q << "DELETE FROM state WHERE " << "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(atime) >= " << timeout_life;
- MYSQL_RES *rows;
if (mysql_query(mysql, q.str().c_str())) {
const char* err=mysql_error(mysql);
- log->error("Error searching for old items: %s", err);
+ log->error("error purging old records: %s", err);
if (isCorrupt(err) && repairTable(mysql,"state")) {
if (mysql_query(mysql, q.str().c_str()))
- log->error("Error re-searching for old items: %s", mysql_error(mysql));
+ log->error("error re-purging old records: %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);
}
- log->debug("cleanup thread exiting...");
+ log->info("cleanup thread exiting...");
mutex->unlock();
delete mutex;
- thread_end();
- Thread::exit(NULL);
+ xmltooling::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();
+#ifndef WIN32
+ // First, let'block all signals
+ xmltooling::Thread::mask_all_signals();
+#endif
// Now run the cleanup process.
cache->cleanup();
return NULL;
}
-bool ShibMySQLCCache::repairTable(MYSQL*& mysql, const char* table)
+class MySQLReplayCache : public MySQLBase, virtual public IReplayCache
{
- 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");
- }
+public:
+ MySQLReplayCache(const DOMElement* e);
+ virtual ~MySQLReplayCache() {}
- if (mysql_query(mysql,
- "CREATE TABLE state (cookie VARCHAR(64) PRIMARY KEY, application_id VARCHAR(255),"
- "atime DATETIME, addr VARCHAR(128), statement TEXT)")) {
- log->error ("Error creating state: %s", mysql_error(mysql));
- throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
- }
+ 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);
+};
- 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");
- }
-}
+MySQLReplayCache::MySQLReplayCache(const DOMElement* e) : MySQLBase(e) {}
-void ShibMySQLCCache::upgradeDatabase(MYSQL* mysql)
+bool MySQLReplayCache::check(const char* str, time_t expires)
{
- if (mysql_query(mysql, "DROP TABLE state")) {
- log->error("Error dropping old session state table: %s", mysql_error(mysql));
+#ifdef _DEBUG
+ xmltooling::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));
+ }
}
-
- if (mysql_query(mysql,
- "CREATE TABLE state (cookie VARCHAR(64) PRIMARY KEY, application_id VARCHAR(255),"
- "atime DATETIME, addr VARCHAR(128), statement TEXT)")) {
- log->error ("Error creating state table: %s", mysql_error(mysql));
- throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
+
+ 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.");
}
- 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");
+ // Did we find it?
+ MYSQL_RES* rows = mysql_store_result(mysql);
+ if (rows && mysql_num_rows(rows)>0) {
+ mysql_free_result(rows);
+ return false;
}
-}
-
-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;
+ ostringstream q3;
+ q3 << "INSERT INTO replay VALUES('" << str << "'," << "FROM_UNIXTIME(" << expires << "))";
- } 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");
+ // 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.");
}
- }
- log->crit("MySQL Read Failed in version verificatoin");
- throw SAMLException("ShibMySQLCCache::getVersion(): error reading version");
-}
-
-void ShibMySQLCCache::mysqlInit(void)
-{
- log->info ("Opening MySQL Database");
-
- // Setup the argument array
- vector<string> arg_array;
- arg_array.push_back("shar");
-
- // grab any MySQL parameters from the config file
- const DOMElement* e=saml::XML::getFirstChildElement(m_root,ShibTargetConfig::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,ShibTargetConfig::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;
-}
-
-/*************************************************************************
- * 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, ISessionCacheEntry* entry, ShibMySQLCCache* cache)
-{
- m_cacheEntry = entry;
- m_key = key;
- m_cache = cache;
-}
-
-bool ShibMySQLCCacheEntry::isValid(time_t lifetime, time_t timeout) const
-{
- bool res = m_cacheEntry->isValid(lifetime, timeout);
- if (res == true)
- res = touch();
- return res;
-}
-
-bool ShibMySQLCCacheEntry::touch() const
-{
- string q=string("UPDATE state SET atime=NOW() WHERE cookie='") + m_key + "'";
-
- 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));
- return false;
- }
- return true;
+
+ return true;
}
/*************************************************************************
IPlugIn* new_mysql_ccache(const DOMElement* e)
{
- return new ShibMySQLCCache(e);
+ return new ShibMySQLCCache(e);
}
-#define PLUGINTYPE "edu.internet2.middleware.shibboleth.target.provider.MySQLSessionCache"
-
-extern "C" int SHIBMYSQL_EXPORTS saml_extension_init(void*)
+IPlugIn* new_mysql_replay(const DOMElement* e)
{
- // register this ccache type
- SAMLConfig::getConfig().getPlugMgr().regFactory(PLUGINTYPE, &new_mysql_ccache);
- return 0;
+ return new MySQLReplayCache(e);
}
-extern "C" void SHIBMYSQL_EXPORTS saml_extension_term()
+extern "C" int SHIBMYSQL_EXPORTS saml_extension_init(void*)
{
- SAMLConfig::getConfig().getPlugMgr().unregFactory(PLUGINTYPE);
+ // register this ccache type
+ SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::MySQLReplayCacheType, &new_mysql_replay);
+ SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::MySQLSessionCacheType, &new_mysql_ccache);
+ return 0;
}
-/*************************************************************************
- * Local Functions
- */
-
-extern "C" void shib_mysql_destroy_handle(void* data)
+extern "C" void SHIBMYSQL_EXPORTS saml_extension_term()
{
- MYSQL* mysql = (MYSQL*) data;
- mysql_close(mysql);
+ // Shutdown MySQL
+ if (g_MySQLInitialized)
+ mysql_server_end();
+ SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::MySQLReplayCacheType);
+ SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::MySQLSessionCacheType);
}