2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * shib-mysql-ccache.cpp: Shibboleth Credential Cache using MySQL.
20 * Created by: Derek Atkins <derek@ihtfp.com>
25 // eventually we might be able to support autoconf via cygwin...
26 #if defined (_MSC_VER) || defined(__BORLANDC__)
27 # include "config_win32.h"
33 # define SHIBMYSQL_EXPORTS __declspec(dllexport)
35 # define SHIBMYSQL_EXPORTS
42 #include <shib-target/shib-target.h>
44 #include <log4cpp/Category.hh>
45 #include <xmltooling/util/NDC.h>
46 #include <xmltooling/util/Threads.h>
47 #include <xmltooling/util/XMLHelper.h>
48 using xmltooling::XMLHelper;
57 // wanted to use MySQL codes for this, but can't seem to get back a 145
58 #define isCorrupt(s) strstr(s,"(errno: 145)")
60 #ifdef HAVE_LIBDMALLOCXX
64 using namespace shibtarget;
65 using namespace opensaml::saml2md;
67 using namespace log4cpp;
70 #define PLUGIN_VER_MAJOR 3
71 #define PLUGIN_VER_MINOR 0
74 "CREATE TABLE state (" \
75 "cookie VARCHAR(64) PRIMARY KEY, " \
76 "application_id VARCHAR(255)," \
79 "addr VARCHAR(128)," \
82 "provider VARCHAR(256)," \
84 "authn_context TEXT," \
87 #define REPLAY_TABLE \
88 "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
89 "expires TIMESTAMP, " \
92 static const XMLCh Argument[] =
93 { chLatin_A, chLatin_r, chLatin_g, chLatin_u, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chNull };
94 static const XMLCh cleanupInterval[] =
95 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
96 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
98 static const XMLCh cacheTimeout[] =
99 { 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 };
100 static const XMLCh mysqlTimeout[] =
101 { 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 };
102 static const XMLCh storeAttributes[] =
103 { 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 };
105 static bool g_MySQLInitialized = false;
107 class MySQLBase : public virtual saml::IPlugIn
110 MySQLBase(const DOMElement* e);
111 virtual ~MySQLBase();
114 bool repairTable(MYSQL*&, const char* table);
116 log4cpp::Category* log;
119 xmltooling::ThreadKey* m_mysql;
120 const DOMElement* m_root; // can only use this during initialization
125 void createDatabase(MYSQL*, int major, int minor);
126 void upgradeDatabase(MYSQL*);
127 pair<int,int> getVersion(MYSQL*);
130 // Forward declarations
131 static void mysqlInit(const DOMElement* e, Category& log);
133 extern "C" void shib_mysql_destroy_handle(void* data)
135 MYSQL* mysql = (MYSQL*) data;
136 if (mysql) mysql_close(mysql);
139 MySQLBase::MySQLBase(const DOMElement* e) : m_root(e)
142 xmltooling::NDC ndc("MySQLBase");
144 log = &(Category::getInstance("shibtarget.SessionCache.MySQL"));
146 m_mysql = xmltooling::ThreadKey::create(&shib_mysql_destroy_handle);
154 MySQLBase::~MySQLBase()
159 MYSQL* MySQLBase::getMYSQL()
162 xmltooling::NDC ndc("getMYSQL");
165 // Do we already have a handle?
166 MYSQL* mysql=reinterpret_cast<MYSQL*>(m_mysql->getData());
170 // Connect to the database
171 mysql = mysql_init(NULL);
173 log->error("mysql_init failed");
175 throw SAMLException("MySQLBase::getMYSQL(): mysql_init() failed");
178 if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shibd", 0, NULL, 0)) {
180 log->crit("mysql_real_connect failed: %s", mysql_error(mysql));
182 throw SAMLException("MySQLBase::getMYSQL(): mysql_real_connect() failed");
185 log->info("mysql_real_connect failed: %s. Trying to create", mysql_error(mysql));
187 // This will throw an exception if it fails.
188 createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
192 pair<int,int> v=getVersion (mysql);
194 // Make sure we've got the right version
195 if (v.first != PLUGIN_VER_MAJOR || v.second != PLUGIN_VER_MINOR) {
197 // If we're capable, try upgrading on the fly...
198 if (v.first == 0 || v.first == 1 || v.first == 2) {
199 if (mysql_query(mysql, "DROP TABLE state")) {
200 log->error("error dropping old session state table: %s", mysql_error(mysql));
202 if (v.first==2 && mysql_query(mysql, "DROP TABLE replay")) {
203 log->error("error dropping old session state table: %s", mysql_error(mysql));
205 upgradeDatabase(mysql);
209 log->crit("Unknown database version: %d.%d", v.first, v.second);
210 throw SAMLException("MySQLBase::getMYSQL(): Unknown database version");
214 // We're all set.. Save off the handle for this thread.
215 m_mysql->setData(mysql);
219 bool MySQLBase::repairTable(MYSQL*& mysql, const char* table)
221 string q = string("REPAIR TABLE ") + table;
222 if (mysql_query(mysql, q.c_str())) {
223 log->error("Error repairing table %s: %s", table, mysql_error(mysql));
227 // seems we have to recycle the connection to get the thread to keep working
228 // other threads seem to be ok, but we should monitor that
230 m_mysql->setData(NULL);
235 void MySQLBase::createDatabase(MYSQL* mysql, int major, int minor)
237 log->info("creating database");
241 ms = mysql_init(NULL);
243 log->crit("mysql_init failed");
244 throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_init failed");
247 if (!mysql_real_connect(ms, NULL, NULL, NULL, NULL, 0, NULL, 0)) {
248 log->crit("cannot open DB file to create DB: %s", mysql_error(ms));
249 throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect failed");
252 if (mysql_query(ms, "CREATE DATABASE shibd")) {
253 log->crit("cannot create shibd database: %s", mysql_error(ms));
254 throw SAMLException("ShibMySQLCCache::createDatabase(): create db cmd failed");
257 if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shibd", 0, NULL, 0)) {
258 log->crit("cannot open shibd database");
259 throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect to plugin db failed");
265 catch (SAMLException&) {
272 // Now create the tables if they don't exist
273 log->info("Creating database tables");
275 if (mysql_query(mysql, "CREATE TABLE version (major INT, minor INT)")) {
276 log->error ("error creating version: %s", mysql_error(mysql));
277 throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
280 if (mysql_query(mysql,STATE_TABLE)) {
281 log->error ("error creating state table: %s", mysql_error(mysql));
282 throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
285 if (mysql_query(mysql,REPLAY_TABLE)) {
286 log->error ("error creating replay table: %s", mysql_error(mysql));
287 throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
291 q << "INSERT INTO version VALUES(" << major << "," << minor << ")";
292 if (mysql_query(mysql, q.str().c_str())) {
293 log->error ("error setting version: %s", mysql_error(mysql));
294 throw SAMLException("ShibMySQLCCache::createDatabase(): version insert failed");
298 void MySQLBase::upgradeDatabase(MYSQL* mysql)
300 if (mysql_query(mysql,STATE_TABLE)) {
301 log->error ("error creating state table: %s", mysql_error(mysql));
302 throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
305 if (mysql_query(mysql,REPLAY_TABLE)) {
306 log->error ("error creating replay table: %s", mysql_error(mysql));
307 throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating replay table");
311 q << "UPDATE version SET major = " << PLUGIN_VER_MAJOR;
312 if (mysql_query(mysql, q.str().c_str())) {
313 log->error ("error updating version: %s", mysql_error(mysql));
314 throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error updating version");
318 pair<int,int> MySQLBase::getVersion(MYSQL* mysql)
320 // grab the version number from the database
321 if (mysql_query(mysql, "SELECT * FROM version")) {
322 log->error("error reading version: %s", mysql_error(mysql));
323 throw SAMLException("MySQLBase::getVersion(): error reading version");
326 MYSQL_RES* rows = mysql_store_result(mysql);
328 if (mysql_num_rows(rows) == 1 && mysql_num_fields(rows) == 2) {
329 MYSQL_ROW row = mysql_fetch_row(rows);
330 int major = row[0] ? atoi(row[0]) : -1;
331 int minor = row[1] ? atoi(row[1]) : -1;
332 log->debug("opening database version %d.%d", major, minor);
333 mysql_free_result(rows);
334 return make_pair(major,minor);
337 // Wrong number of rows or wrong number of fields...
338 log->crit("Houston, we've got a problem with the database...");
339 mysql_free_result(rows);
340 throw SAMLException("MySQLBase::getVersion(): version verification failed");
343 log->crit("MySQL Read Failed in version verification");
344 throw SAMLException("MySQLBase::getVersion(): error reading version");
347 static void mysqlInit(const DOMElement* e, Category& log)
349 if (g_MySQLInitialized) {
350 log.info("MySQL embedded server already initialized");
353 log.info("initializing MySQL embedded server");
355 // Setup the argument array
356 vector<string> arg_array;
357 arg_array.push_back("shibboleth");
359 // grab any MySQL parameters from the config file
360 e=XMLHelper::getFirstChildElement(e,Argument);
362 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
364 arg_array.push_back(arg.get());
365 e=XMLHelper::getNextSiblingElement(e,Argument);
368 // Compute the argument array
369 vector<string>::size_type arg_count = arg_array.size();
370 const char** args=new const char*[arg_count];
371 for (vector<string>::size_type i = 0; i < arg_count; i++)
372 args[i] = arg_array[i].c_str();
374 // Initialize MySQL with the arguments
375 mysql_server_init(arg_count, (char **)args, NULL);
378 g_MySQLInitialized = true;
381 class ShibMySQLCCache : public MySQLBase, virtual public ISessionCache, virtual public ISessionCacheStore
384 ShibMySQLCCache(const DOMElement* e);
385 virtual ~ShibMySQLCCache();
387 // Delegate all the ISessionCache methods.
389 const IApplication* application,
390 const RoleDescriptor* role,
391 const char* client_addr,
392 const SAMLSubject* subject,
393 const char* authnContext,
394 const SAMLResponse* tokens
396 { return m_cache->insert(application,role,client_addr,subject,authnContext,tokens); }
397 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
398 { return m_cache->find(key,application,client_addr); }
399 void remove(const char* key, const IApplication* application, const char* client_addr)
400 { m_cache->remove(key,application,client_addr); }
402 bool setBackingStore(ISessionCacheStore*) {return false;}
404 // Store methods handle the database work
407 const IApplication* application,
408 const ISessionCacheEntry* entry,
415 string& applicationId,
416 string& clientAddress,
419 string& authnContext,
426 HRESULT onRead(const char* key, time_t& accessed);
427 HRESULT onRead(const char* key, string& tokens);
428 HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
429 HRESULT onDelete(const char* key);
434 bool m_storeAttributes;
435 ISessionCache* m_cache;
436 xmltooling::CondWait* shutdown_wait;
438 xmltooling::Thread* cleanup_thread;
440 static void* cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
443 ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e) : MySQLBase(e), m_storeAttributes(false)
446 xmltooling::NDC ndc("ShibMySQLCCache");
449 m_cache = dynamic_cast<ISessionCache*>(
450 SAMLConfig::getConfig().getPlugMgr().newPlugin(MEMORY_SESSIONCACHE, e)
452 if (!m_cache->setBackingStore(this)) {
454 throw SAMLException("Unable to register MySQL cache plugin as a cache store.");
457 shutdown_wait = xmltooling::CondWait::create();
460 // Load our configuration details...
461 const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
462 if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
463 m_storeAttributes=true;
465 // Initialize the cleanup thread
466 cleanup_thread = xmltooling::Thread::create(&cleanup_fcn, (void*)this);
469 ShibMySQLCCache::~ShibMySQLCCache()
472 shutdown_wait->signal();
473 cleanup_thread->join(NULL);
477 HRESULT ShibMySQLCCache::onCreate(
479 const IApplication* application,
480 const ISessionCacheEntry* entry,
487 xmltooling::NDC ndc("onCreate");
490 // Get XML data from entry. Default is not to return SAML objects.
491 const char* context=entry->getAuthnContext();
492 pair<const char*,const SAMLSubject*> subject=entry->getSubject();
493 pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
496 q << "INSERT INTO state VALUES('" << key << "','" << application->getId() << "',";
498 q << "NOW(),NOW(),'";
500 q << "FROM_UNIXTIME(" << created << "),NOW(),'";
501 q << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId() << "','"
502 << subject.first << "','" << context << "',";
504 if (m_storeAttributes && tokens.first)
505 q << "'" << tokens.first << "')";
509 if (log->isDebugEnabled())
510 log->debug("SQL insert: %s", q.str().c_str());
512 // then add it to the database
513 MYSQL* mysql = getMYSQL();
514 if (mysql_query(mysql, q.str().c_str())) {
515 const char* err=mysql_error(mysql);
516 log->error("error inserting %s: %s", key, err);
517 if (isCorrupt(err) && repairTable(mysql,"state")) {
519 if (mysql_query(mysql, q.str().c_str())) {
520 log->error("error inserting %s: %s", key, mysql_error(mysql));
531 HRESULT ShibMySQLCCache::onRead(
533 string& applicationId,
534 string& clientAddress,
537 string& authnContext,
546 xmltooling::NDC ndc("onRead");
549 log->debug("searching MySQL database...");
551 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";
553 MYSQL* mysql = getMYSQL();
554 if (mysql_query(mysql, q.c_str())) {
555 const char* err=mysql_error(mysql);
556 log->error("error searching for %s: %s", key, err);
557 if (isCorrupt(err) && repairTable(mysql,"state")) {
558 if (mysql_query(mysql, q.c_str()))
559 log->error("error retrying search for %s: %s", key, mysql_error(mysql));
563 MYSQL_RES* rows = mysql_store_result(mysql);
565 // Nope, doesn't exist.
566 if (!rows || mysql_num_rows(rows)==0) {
567 log->debug("not found in database");
569 mysql_free_result(rows);
573 // Make sure we got 1 and only 1 row.
574 if (mysql_num_rows(rows) > 1) {
575 log->error("database select returned %d rows!", mysql_num_rows(rows));
576 mysql_free_result(rows);
580 log->debug("session found, tranfering data back into memory");
595 MYSQL_ROW row = mysql_fetch_row(rows);
596 applicationId=row[0];
597 created=atoi(row[1]);
598 accessed=atoi(row[2]);
599 clientAddress=row[3];
600 majorVersion=atoi(row[4]);
601 minorVersion=atoi(row[5]);
609 mysql_free_result(rows);
614 HRESULT ShibMySQLCCache::onRead(const char* key, time_t& accessed)
617 xmltooling::NDC ndc("onRead");
620 log->debug("reading last access time from MySQL database");
622 string q = string("SELECT UNIX_TIMESTAMP(atime) FROM state WHERE cookie='") + key + "' LIMIT 1";
624 MYSQL* mysql = getMYSQL();
625 if (mysql_query(mysql, q.c_str())) {
626 const char* err=mysql_error(mysql);
627 log->error("error searching for %s: %s", key, err);
628 if (isCorrupt(err) && repairTable(mysql,"state")) {
629 if (mysql_query(mysql, q.c_str()))
630 log->error("error retrying search for %s: %s", key, mysql_error(mysql));
634 MYSQL_RES* rows = mysql_store_result(mysql);
636 // Nope, doesn't exist.
637 if (!rows || mysql_num_rows(rows)==0) {
638 log->warn("session expected, but not found in database");
640 mysql_free_result(rows);
644 // Make sure we got 1 and only 1 row.
645 if (mysql_num_rows(rows) != 1) {
646 log->error("database select returned %d rows!", mysql_num_rows(rows));
647 mysql_free_result(rows);
651 MYSQL_ROW row = mysql_fetch_row(rows);
652 accessed=atoi(row[0]);
655 mysql_free_result(rows);
660 HRESULT ShibMySQLCCache::onRead(const char* key, string& tokens)
663 xmltooling::NDC ndc("onRead");
666 if (!m_storeAttributes)
669 log->debug("reading cached tokens from MySQL database");
671 string q = string("SELECT tokens FROM state WHERE cookie='") + key + "' LIMIT 1";
673 MYSQL* mysql = getMYSQL();
674 if (mysql_query(mysql, q.c_str())) {
675 const char* err=mysql_error(mysql);
676 log->error("error searching for %s: %s", key, err);
677 if (isCorrupt(err) && repairTable(mysql,"state")) {
678 if (mysql_query(mysql, q.c_str()))
679 log->error("error retrying search for %s: %s", key, mysql_error(mysql));
683 MYSQL_RES* rows = mysql_store_result(mysql);
685 // Nope, doesn't exist.
686 if (!rows || mysql_num_rows(rows)==0) {
687 log->warn("session expected, but not found in database");
689 mysql_free_result(rows);
693 // Make sure we got 1 and only 1 row.
694 if (mysql_num_rows(rows) != 1) {
695 log->error("database select returned %d rows!", mysql_num_rows(rows));
696 mysql_free_result(rows);
700 MYSQL_ROW row = mysql_fetch_row(rows);
705 mysql_free_result(rows);
710 HRESULT ShibMySQLCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
713 xmltooling::NDC ndc("onUpdate");
718 q << "UPDATE state SET atime=FROM_UNIXTIME(" << lastAccess << ")";
720 if (!m_storeAttributes)
722 q << "UPDATE state SET tokens=";
724 q << "'" << tokens << "'";
729 log->warn("onUpdate called with nothing to do!");
733 q << " WHERE cookie='" << key << "'";
735 MYSQL* mysql = getMYSQL();
736 if (mysql_query(mysql, q.str().c_str())) {
737 const char* err=mysql_error(mysql);
738 log->error("error updating %s: %s", key, err);
739 if (isCorrupt(err) && repairTable(mysql,"state")) {
741 if (mysql_query(mysql, q.str().c_str())) {
742 log->error("error updating %s: %s", key, mysql_error(mysql));
753 HRESULT ShibMySQLCCache::onDelete(const char* key)
756 xmltooling::NDC ndc("onDelete");
759 // Remove from the database
760 string q = string("DELETE FROM state WHERE cookie='") + key + "'";
761 MYSQL* mysql = getMYSQL();
762 if (mysql_query(mysql, q.c_str())) {
763 const char* err=mysql_error(mysql);
764 log->error("error deleting entry %s: %s", key, err);
765 if (isCorrupt(err) && repairTable(mysql,"state")) {
767 if (mysql_query(mysql, q.c_str())) {
768 log->error("error deleting entry %s: %s", key, mysql_error(mysql));
779 void ShibMySQLCCache::cleanup()
782 xmltooling::NDC ndc("cleanup");
785 xmltooling::Mutex* mutex = xmltooling::Mutex::create();
788 int timeout_life = 0;
790 // Load our configuration details...
791 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
793 rerun_timer = XMLString::parseInt(tag);
795 // search for 'mysql-cache-timeout' and then the regular cache timeout
796 tag=m_root->getAttributeNS(NULL,mysqlTimeout);
798 timeout_life = XMLString::parseInt(tag);
800 tag=m_root->getAttributeNS(NULL,cacheTimeout);
802 timeout_life = XMLString::parseInt(tag);
805 if (rerun_timer <= 0)
806 rerun_timer = 300; // rerun every 5 minutes
808 if (timeout_life <= 0)
809 timeout_life = 28800; // timeout after 8 hours
813 MYSQL* mysql = getMYSQL();
815 log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
817 while (shutdown == false) {
818 shutdown_wait->timedwait(mutex, rerun_timer);
820 if (shutdown == true)
823 // Find all the entries in the database that haven't been used
824 // recently In particular, find all entries that have not been
825 // accessed in 'timeout_life' seconds.
827 q << "DELETE FROM state WHERE " << "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(atime) >= " << timeout_life;
829 if (mysql_query(mysql, q.str().c_str())) {
830 const char* err=mysql_error(mysql);
831 log->error("error purging old records: %s", err);
832 if (isCorrupt(err) && repairTable(mysql,"state")) {
833 if (mysql_query(mysql, q.str().c_str()))
834 log->error("error re-purging old records: %s", mysql_error(mysql));
839 log->info("cleanup thread exiting...");
843 xmltooling::Thread::exit(NULL);
846 void* ShibMySQLCCache::cleanup_fcn(void* cache_p)
848 ShibMySQLCCache* cache = (ShibMySQLCCache*)cache_p;
851 // First, let'block all signals
852 xmltooling::Thread::mask_all_signals();
855 // Now run the cleanup process.
860 class MySQLReplayCache : public MySQLBase, virtual public IReplayCache
863 MySQLReplayCache(const DOMElement* e);
864 virtual ~MySQLReplayCache() {}
866 bool check(const XMLCh* str, time_t expires) {auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
867 bool check(const char* str, time_t expires);
870 MySQLReplayCache::MySQLReplayCache(const DOMElement* e) : MySQLBase(e) {}
872 bool MySQLReplayCache::check(const char* str, time_t expires)
875 xmltooling::NDC ndc("check");
878 // Remove expired entries
879 string q = string("DELETE FROM replay WHERE expires < NOW()");
880 MYSQL* mysql = getMYSQL();
881 if (mysql_query(mysql, q.c_str())) {
882 const char* err=mysql_error(mysql);
883 log->error("Error deleting expired entries: %s", err);
884 if (isCorrupt(err) && repairTable(mysql,"replay")) {
886 if (mysql_query(mysql, q.c_str()))
887 log->error("Error deleting expired entries: %s", mysql_error(mysql));
891 string q2 = string("SELECT id FROM replay WHERE id='") + str + "'";
892 if (mysql_query(mysql, q2.c_str())) {
893 const char* err=mysql_error(mysql);
894 log->error("Error searching for %s: %s", str, err);
895 if (isCorrupt(err) && repairTable(mysql,"replay")) {
896 if (mysql_query(mysql, q2.c_str())) {
897 log->error("Error retrying search for %s: %s", str, mysql_error(mysql));
898 throw SAMLException("Replay cache failed, please inform application support staff.");
902 throw SAMLException("Replay cache failed, please inform application support staff.");
906 MYSQL_RES* rows = mysql_store_result(mysql);
907 if (rows && mysql_num_rows(rows)>0) {
908 mysql_free_result(rows);
913 q3 << "INSERT INTO replay VALUES('" << str << "'," << "FROM_UNIXTIME(" << expires << "))";
915 // then add it to the database
916 if (mysql_query(mysql, q3.str().c_str())) {
917 const char* err=mysql_error(mysql);
918 log->error("Error inserting %s: %s", str, err);
919 if (isCorrupt(err) && repairTable(mysql,"state")) {
921 if (mysql_query(mysql, q3.str().c_str())) {
922 log->error("Error inserting %s: %s", str, mysql_error(mysql));
923 throw SAMLException("Replay cache failed, please inform application support staff.");
927 throw SAMLException("Replay cache failed, please inform application support staff.");
933 /*************************************************************************
934 * The registration functions here...
937 IPlugIn* new_mysql_ccache(const DOMElement* e)
939 return new ShibMySQLCCache(e);
942 IPlugIn* new_mysql_replay(const DOMElement* e)
944 return new MySQLReplayCache(e);
947 extern "C" int SHIBMYSQL_EXPORTS saml_extension_init(void*)
949 // register this ccache type
950 SAMLConfig::getConfig().getPlugMgr().regFactory(MYSQL_REPLAYCACHE, &new_mysql_replay);
951 SAMLConfig::getConfig().getPlugMgr().regFactory(MYSQL_SESSIONCACHE, &new_mysql_ccache);
955 extern "C" void SHIBMYSQL_EXPORTS saml_extension_term()
958 if (g_MySQLInitialized)
960 SAMLConfig::getConfig().getPlugMgr().unregFactory(MYSQL_REPLAYCACHE);
961 SAMLConfig::getConfig().getPlugMgr().unregFactory(MYSQL_SESSIONCACHE);