Prepare for addition of replay cache.
[shibboleth/cpp-sp.git] / shib-mysql-ccache / shib-mysql-ccache.cpp
1 /*
2  * shib-mysql-ccache.cpp: Shibboleth Credential Cache using MySQL.
3  *
4  * Created by:  Derek Atkins <derek@ihtfp.com>
5  *
6  * $Id$
7  */
8
9 /* This file is loosely based off the Shibboleth Credential Cache.
10  * This plug-in is designed as a two-layer cache.  Layer 1, the
11  * long-term cache, stores data in a MySQL embedded database.  The
12  * data stored in layer 1 is only the session id (cookie), the
13  * "posted" SAML statement (expanded into an XML string), and usage
14  * timestamps.
15  *
16  * Short-term data is cached in memory as SAML objects in the layer 2
17  * cache.  Data like Attribute Authority assertions are stored in
18  * the layer 2 cache.
19  */
20
21 // eventually we might be able to support autoconf via cygwin...
22 #if defined (_MSC_VER) || defined(__BORLANDC__)
23 # include "config_win32.h"
24 #else
25 # include "config.h"
26 #endif
27
28 #ifdef WIN32
29 # define SHIBMYSQL_EXPORTS __declspec(dllexport)
30 #else
31 # define SHIBMYSQL_EXPORTS
32 #endif
33
34 #ifdef HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
37
38 #include <shib-target/shib-target.h>
39 #include <shib/shib-threads.h>
40 #include <log4cpp/Category.hh>
41
42 #include <sstream>
43 #include <stdexcept>
44
45 #include <mysql.h>
46
47 // wanted to use MySQL codes for this, but can't seem to get back a 145
48 #define isCorrupt(s) strstr(s,"(errno: 145)")
49
50 #ifdef HAVE_LIBDMALLOCXX
51 #include <dmalloc.h>
52 #endif
53
54 using namespace std;
55 using namespace saml;
56 using namespace shibboleth;
57 using namespace shibtarget;
58 using namespace log4cpp;
59
60 #define PLUGIN_VER_MAJOR 1
61 #define PLUGIN_VER_MINOR 0
62
63 static const XMLCh Argument[] =
64 { chLatin_A, chLatin_r, chLatin_g, chLatin_u, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chNull };
65 static const XMLCh cleanupInterval[] =
66 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
67   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
68 };
69 static const XMLCh cacheTimeout[] =
70 { 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 };
71 static const XMLCh mysqlTimeout[] =
72 { 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 };
73
74 class ShibMySQLCCache;
75 class ShibMySQLCCacheEntry : public ISessionCacheEntry
76 {
77 public:
78   ShibMySQLCCacheEntry(const char*, ISessionCacheEntry*, ShibMySQLCCache*);
79   ~ShibMySQLCCacheEntry() {}
80
81   virtual void lock() {}
82   virtual void unlock() { m_cacheEntry->unlock(); delete this; }
83   virtual bool isValid(time_t lifetime, time_t timeout) const;
84   virtual const char* getClientAddress() const { return m_cacheEntry->getClientAddress(); }
85   virtual const SAMLAuthenticationStatement* getAuthnStatement() const { return m_cacheEntry->getAuthnStatement(); }
86   virtual Iterator<SAMLAssertion*> getAssertions() { return m_cacheEntry->getAssertions(); }
87
88 private:
89   bool touch() const;
90
91   ShibMySQLCCache* m_cache;
92   ISessionCacheEntry* m_cacheEntry;
93   string m_key;
94 };
95
96 class ShibMySQLCCache : public ISessionCache
97 {
98 public:
99   ShibMySQLCCache(const DOMElement* e);
100   virtual ~ShibMySQLCCache();
101
102   virtual void thread_init();
103   virtual void thread_end() {}
104
105   virtual string generateKey() const {return m_cache->generateKey();}
106   virtual ISessionCacheEntry* find(const char* key, const IApplication* application);
107   virtual void insert(
108         const char* key,
109         const IApplication* application,
110         SAMLAuthenticationStatement *s,
111         const char *client_addr,
112         SAMLResponse* r=NULL,
113         const IRoleDescriptor* source=NULL);
114   virtual void remove(const char* key);
115
116   void  cleanup();
117   MYSQL* getMYSQL() const;
118
119   log4cpp::Category* log;
120
121 private:
122   ISessionCache* m_cache;
123   ThreadKey* m_mysql;
124   const DOMElement* m_root; // can only use this during initialization
125
126   static void*  cleanup_fcn(void*); // XXX Assumed an ShibMySQLCCache
127   CondWait* shutdown_wait;
128   bool shutdown;
129   Thread* cleanup_thread;
130
131   bool initialized;
132
133   void createDatabase(MYSQL*, int major, int minor);
134   void upgradeDatabase(MYSQL*);
135   void getVersion(MYSQL*, int* major_p, int* minor_p);
136   bool repairTable(MYSQL*&, const char* table);
137 };
138
139 // Forward declarations
140 extern "C" void shib_mysql_destroy_handle(void* data);
141 void mysqlInit(const DOMElement* e, Category& log);
142
143 /*************************************************************************
144  * The CCache here talks to a MySQL database.  The database stores
145  * three items: the cookie (session key index), the lastAccess time, and
146  * the SAMLAuthenticationStatement.  All other access is performed
147  * through the memory cache provided by shibboleth.
148  */
149
150 MYSQL* ShibMySQLCCache::getMYSQL() const
151 {
152   return (MYSQL*)m_mysql->getData();
153 }
154
155 void ShibMySQLCCache::thread_init()
156 {
157 #ifdef _DEBUG
158   saml::NDC ndc("thread_init");
159 #endif
160
161   // Connect to the database
162   MYSQL* mysql = mysql_init(NULL);
163   if (!mysql) {
164     log->error("mysql_init failed");
165     mysql_close(mysql);
166     throw SAMLException("ShibMySQLCCache::thread_init(): mysql_init() failed");
167   }
168
169   if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
170     if (initialized) {
171       log->crit("mysql_real_connect failed: %s", mysql_error(mysql));
172       mysql_close(mysql);
173       throw SAMLException("ShibMySQLCCache::thread_init(): mysql_real_connect() failed");
174     } else {
175       log->info("mysql_real_connect failed: %s.  Trying to create", mysql_error(mysql));
176
177       // This will throw an exception if it fails.
178       createDatabase(mysql, PLUGIN_VER_MAJOR, PLUGIN_VER_MINOR);
179     }
180   }
181
182   int major = -1, minor = -1;
183   getVersion (mysql, &major, &minor);
184
185   // Make sure we've got the right version
186   if (major != PLUGIN_VER_MAJOR || minor != PLUGIN_VER_MINOR) {
187    
188     // If we're capable, try upgrading on the fly...
189     if (major == 0 && minor == 0) {
190        upgradeDatabase(mysql);
191     }
192     else {
193         mysql_close(mysql);
194         log->crit("Invalid database version: %d.%d", major, minor);
195         throw SAMLException("ShibMySQLCCache::thread_init(): Invalid database version");
196     }
197   }
198
199   // We're all set.. Save off the handle for this thread.
200   m_mysql->setData(mysql);
201 }
202
203 ShibMySQLCCache::ShibMySQLCCache(const DOMElement* e)
204 {
205 #ifdef _DEBUG
206   saml::NDC ndc("shibmysql::ShibMySQLCCache");
207 #endif
208
209   m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
210   log = &(Category::getInstance("shibmysql::ShibMySQLCCache"));
211
212   m_root=e;
213   initialized = false;
214   mysqlInit(e,*log);
215   thread_init();
216   initialized = true;
217
218   m_cache = dynamic_cast<ISessionCache*>(
219       SAMLConfig::getConfig().getPlugMgr().newPlugin(
220         "edu.internet2.middleware.shibboleth.sp.provider.MemorySessionCacheProvider", e
221         )
222     );
223
224   // Initialize the cleanup thread
225   shutdown_wait = CondWait::create();
226   shutdown = false;
227   cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
228 }
229
230 ShibMySQLCCache::~ShibMySQLCCache()
231 {
232   shutdown = true;
233   shutdown_wait->signal();
234   cleanup_thread->join(NULL);
235
236   thread_end();
237   delete m_cache;
238   delete m_mysql;
239
240   // Shutdown MySQL
241   mysql_server_end();
242 }
243
244 ISessionCacheEntry* ShibMySQLCCache::find(const char* key, const IApplication* application)
245 {
246 #ifdef _DEBUG
247   saml::NDC ndc("ShibMySQLCCache::find");
248 #endif
249
250   ISessionCacheEntry* res = m_cache->find(key, application);
251   if (!res) {
252
253     log->debug("Looking in database...");
254
255     // nothing cached; see if this exists in the database
256     string q = string("SELECT application_id,addr,statement FROM state WHERE cookie='") + key + "' LIMIT 1";
257
258     MYSQL_RES* rows;
259     MYSQL* mysql = getMYSQL();
260     if (mysql_query(mysql, q.c_str())) {
261       const char* err=mysql_error(mysql);
262       log->error("Error searching for %s: %s", key, err);
263       if (isCorrupt(err) && repairTable(mysql,"state")) {
264         if (mysql_query(mysql, q.c_str()))
265           log->error("Error retrying search for %s: %s", key, mysql_error(mysql));
266       }
267     }
268
269     rows = mysql_store_result(mysql);
270
271     // Nope, doesn't exist.
272     if (!rows)
273       return NULL;
274
275     // Make sure we got 1 and only 1 rows.
276     if (mysql_num_rows(rows) != 1) {
277       log->error("Select returned wrong number of rows: %d", mysql_num_rows(rows));
278       mysql_free_result(rows);
279       return NULL;
280     }
281
282     log->debug("Match found.  Parsing...");
283
284     // Pull apart the row and process the results
285     MYSQL_ROW row = mysql_fetch_row(rows);
286     IConfig* conf=ShibTargetConfig::getConfig().getINI();
287     Locker locker(conf);
288     const IApplication* application=conf->getApplication(row[0]);
289     if (!application) {
290         mysql_free_result(rows);
291         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"unable to locate application for session, deleted?");
292     }
293     else if (strcmp(row[0],application->getId())) {
294         log->crit("An application (%s) attempted to access another application's (%s) session!", application->getId(), row[0]);
295         mysql_free_result(rows);
296         return NULL;
297     }
298
299     istringstream str(row[2]);
300     SAMLAuthenticationStatement *s = NULL;
301
302     // Try to parse the AuthStatement
303     try {
304       s = new SAMLAuthenticationStatement(str);
305     } catch (...) {
306       mysql_free_result(rows);
307       throw;
308     }
309
310     // Insert it into the memory cache
311     if (s)
312       m_cache->insert(key, application, s, row[1]);
313
314     // Free the results, and then re-run the 'find' query
315     mysql_free_result(rows);
316     res = m_cache->find(key,application);
317     if (!res)
318       return NULL;
319   }
320
321   return new ShibMySQLCCacheEntry(key, res, this);
322 }
323
324 void ShibMySQLCCache::insert(
325     const char* key,
326     const IApplication* application,
327     saml::SAMLAuthenticationStatement *s,
328     const char *client_addr,
329     saml::SAMLResponse* r,
330     const IRoleDescriptor* source)
331 {
332 #ifdef _DEBUG
333   saml::NDC ndc("ShibMySQLCCache::insert");
334 #endif
335   ostringstream os;
336   os << *s;
337
338   string q = string("INSERT INTO state VALUES('") + key + "','" + application->getId() + "',NOW(),'" + client_addr + "','" + os.str() + "')";
339
340   log->debug("Query: %s", q.c_str());
341
342   // then add it to the database
343   MYSQL* mysql = getMYSQL();
344   if (mysql_query(mysql, q.c_str())) {
345     const char* err=mysql_error(mysql);
346     log->error("Error inserting %s: %s", key, err);
347     if (isCorrupt(err) && repairTable(mysql,"state")) {
348         // Try again...
349         if (mysql_query(mysql, q.c_str()))
350           log->error("Error inserting %s: %s", key, mysql_error(mysql));
351           throw SAMLException("ShibMySQLCCache::insert(): inset failed");
352     }
353   }
354
355   // Add it to the memory cache
356   m_cache->insert(key, application, s, client_addr, r, source);
357 }
358
359 void ShibMySQLCCache::remove(const char* key)
360 {
361 #ifdef _DEBUG
362   saml::NDC ndc("ShibMySQLCCache::remove");
363 #endif
364
365   // Remove the cached version
366   m_cache->remove(key);
367
368   // Remove from the database
369   string q = string("DELETE FROM state WHERE cookie='") + key + "'";
370   MYSQL* mysql = getMYSQL();
371   if (mysql_query(mysql, q.c_str())) {
372     const char* err=mysql_error(mysql);
373     log->error("Error deleting entry %s: %s", key, err);
374     if (isCorrupt(err) && repairTable(mysql,"state")) {
375         // Try again...
376         if (mysql_query(mysql, q.c_str()))
377           log->error("Error deleting entry %s: %s", key, mysql_error(mysql));
378     }
379   }
380 }
381
382 void ShibMySQLCCache::cleanup()
383 {
384 #ifdef _DEBUG
385   saml::NDC ndc("ShibMySQLCCache::cleanup");
386 #endif
387
388   Mutex* mutex = Mutex::create();
389   thread_init();
390
391   int rerun_timer = 0;
392   int timeout_life = 0;
393
394   // Load our configuration details...
395   const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
396   if (tag && *tag)
397     rerun_timer = XMLString::parseInt(tag);
398
399   // search for 'mysql-cache-timeout' and then the regular cache timeout
400   tag=m_root->getAttributeNS(NULL,mysqlTimeout);
401   if (tag && *tag)
402     timeout_life = XMLString::parseInt(tag);
403   else {
404       tag=m_root->getAttributeNS(NULL,cacheTimeout);
405       if (tag && *tag)
406         timeout_life = XMLString::parseInt(tag);
407   }
408   
409   if (rerun_timer <= 0)
410     rerun_timer = 300;          // rerun every 5 minutes
411
412   if (timeout_life <= 0)
413     timeout_life = 28800;       // timeout after 8 hours
414
415   mutex->lock();
416
417   MYSQL* mysql = getMYSQL();
418
419   while (shutdown == false) {
420     shutdown_wait->timedwait(mutex, rerun_timer);
421
422     if (shutdown == true)
423       break;
424
425     // Find all the entries in the database that haven't been used
426     // recently In particular, find all entries that have not been
427     // accessed in 'timeout_life' seconds.
428     ostringstream q;
429     q << "SELECT cookie FROM state WHERE " <<
430       "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(atime) >= " << timeout_life;
431
432     MYSQL_RES *rows;
433     if (mysql_query(mysql, q.str().c_str())) {
434       const char* err=mysql_error(mysql);
435       log->error("Error searching for old items: %s", err);
436         if (isCorrupt(err) && repairTable(mysql,"state")) {
437           if (mysql_query(mysql, q.str().c_str()))
438             log->error("Error re-searching for old items: %s", mysql_error(mysql));
439         }
440     }
441
442     rows = mysql_store_result(mysql);
443     if (!rows)
444       continue;
445
446     if (mysql_num_fields(rows) != 1) {
447       log->error("Wrong number of rows, 1 != %d", mysql_num_fields(rows));
448       mysql_free_result(rows);
449       continue;
450     }
451
452     // For each row, remove the entry from the database.
453     MYSQL_ROW row;
454     while ((row = mysql_fetch_row(rows)) != NULL)
455       remove(row[0]);
456
457     mysql_free_result(rows);
458   }
459
460   log->debug("cleanup thread exiting...");
461
462   mutex->unlock();
463   delete mutex;
464   thread_end();
465   Thread::exit(NULL);
466 }
467
468 void* ShibMySQLCCache::cleanup_fcn(void* cache_p)
469 {
470   ShibMySQLCCache* cache = (ShibMySQLCCache*)cache_p;
471
472   // First, let's block all signals
473   Thread::mask_all_signals();
474
475   // Now run the cleanup process.
476   cache->cleanup();
477   return NULL;
478 }
479
480 bool ShibMySQLCCache::repairTable(MYSQL*& mysql, const char* table)
481 {
482   string q = string("REPAIR TABLE ") + table;
483   if (mysql_query(mysql, q.c_str())) {
484     log->error("Error repairing table %s: %s", table, mysql_error(mysql));
485     return false;
486   }
487
488   // seems we have to recycle the connection to get the thread to keep working
489   // other threads seem to be ok, but we should monitor that
490   mysql_close(mysql);
491   m_mysql->setData(NULL);
492   thread_init();
493   mysql=getMYSQL();
494   return true;
495 }
496
497 void ShibMySQLCCache::createDatabase(MYSQL* mysql, int major, int minor)
498 {
499   log->info("Creating database.");
500
501   MYSQL* ms = NULL;
502   try {
503     ms = mysql_init(NULL);
504     if (!ms) {
505       log->crit("mysql_init failed");
506       throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_init failed");
507     }
508
509     if (!mysql_real_connect(ms, NULL, NULL, NULL, NULL, 0, NULL, 0)) {
510       log->crit("cannot open DB file to create DB: %s", mysql_error(ms));
511       throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect failed");
512     }
513
514     if (mysql_query(ms, "CREATE DATABASE shar")) {
515       log->crit("cannot create shar database: %s", mysql_error(ms));
516       throw SAMLException("ShibMySQLCCache::createDatabase(): create db cmd failed");
517     }
518
519     if (!mysql_real_connect(mysql, NULL, NULL, NULL, "shar", 0, NULL, 0)) {
520       log->crit("cannot open SHAR database");
521       throw SAMLException("ShibMySQLCCache::createDatabase(): mysql_real_connect to shar db failed");
522     }
523
524     mysql_close(ms);
525     
526   }
527   catch (SAMLException&) {
528     if (ms)
529       mysql_close(ms);
530     mysql_close(mysql);
531     throw;
532   }
533
534   // Now create the tables if they don't exist
535   log->info("Creating database tables");
536
537   if (mysql_query(mysql, "CREATE TABLE version (major INT, minor INT)")) {
538     log->error ("Error creating version: %s", mysql_error(mysql));
539     throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
540   }
541
542   if (mysql_query(mysql,
543                   "CREATE TABLE state (cookie VARCHAR(64) PRIMARY KEY, application_id VARCHAR(255),"
544                   "atime DATETIME, addr VARCHAR(128), statement TEXT)")) {
545     log->error ("Error creating state: %s", mysql_error(mysql));
546     throw SAMLException("ShibMySQLCCache::createDatabase(): create table cmd failed");
547   }
548
549   ostringstream q;
550   q << "INSERT INTO version VALUES(" << major << "," << minor << ")";
551   if (mysql_query(mysql, q.str().c_str())) {
552     log->error ("Error setting version: %s", mysql_error(mysql));
553     throw SAMLException("ShibMySQLCCache::createDatabase(): version insert failed");
554   }
555 }
556
557 void ShibMySQLCCache::upgradeDatabase(MYSQL* mysql)
558 {
559     if (mysql_query(mysql, "DROP TABLE state")) {
560         log->error("Error dropping old session state table: %s", mysql_error(mysql));
561     }
562
563     if (mysql_query(mysql,
564         "CREATE TABLE state (cookie VARCHAR(64) PRIMARY KEY, application_id VARCHAR(255),"
565        "atime DATETIME, addr VARCHAR(128), statement TEXT)")) {
566         log->error ("Error creating state table: %s", mysql_error(mysql));
567         throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error creating state table");
568     }
569
570     ostringstream q;
571     q << "UPDATE version SET major = " << PLUGIN_VER_MAJOR;
572     if (mysql_query(mysql, q.str().c_str())) {
573         log->error ("Error updating version: %s", mysql_error(mysql));
574         throw SAMLException("ShibMySQLCCache::upgradeDatabase(): error updating version");
575     }
576 }
577
578 void ShibMySQLCCache::getVersion(MYSQL* mysql, int* major_p, int* minor_p)
579 {
580   // grab the version number from the database
581   if (mysql_query(mysql, "SELECT * FROM version"))
582     log->error ("Error reading version: %s", mysql_error(mysql));
583
584   MYSQL_RES* rows = mysql_store_result(mysql);
585   if (rows) {
586     if (mysql_num_rows(rows) == 1 && mysql_num_fields(rows) == 2)  {
587       MYSQL_ROW row = mysql_fetch_row(rows);
588
589       int major = row[0] ? atoi(row[0]) : -1;
590       int minor = row[1] ? atoi(row[1]) : -1;
591       log->debug("opening database version %d.%d", major, minor);
592       
593       mysql_free_result (rows);
594
595       *major_p = major;
596       *minor_p = minor;
597       return;
598
599     } else {
600       // Wrong number of rows or wrong number of fields...
601
602       log->crit("Houston, we've got a problem with the database...");
603       mysql_free_result (rows);
604       throw SAMLException("ShibMySQLCCache::getVersion(): version verification failed");
605     }
606   }
607   log->crit("MySQL Read Failed in version verificatoin");
608   throw SAMLException("ShibMySQLCCache::getVersion(): error reading version");
609 }
610
611 void mysqlInit(const DOMElement* e, Category& log)
612 {
613   static bool done = false;
614   if (done) {
615     log.info("MySQL embedded server already initialized");
616     return;
617   }
618   log.info("initializing MySQL embedded server");
619
620   // Setup the argument array
621   vector<string> arg_array;
622   arg_array.push_back("shibboleth");
623
624   // grab any MySQL parameters from the config file
625   e=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,Argument);
626   while (e) {
627       auto_ptr_char arg(e->getFirstChild()->getNodeValue());
628       if (arg.get())
629           arg_array.push_back(arg.get());
630       e=saml::XML::getNextSiblingElement(e,ShibTargetConfig::SHIBTARGET_NS,Argument);
631   }
632
633   // Compute the argument array
634   int arg_count = arg_array.size();
635   const char** args=new const char*[arg_count];
636   for (int i = 0; i < arg_count; i++)
637     args[i] = arg_array[i].c_str();
638
639   // Initialize MySQL with the arguments
640   mysql_server_init(arg_count, (char **)args, NULL);
641
642   delete[] args;
643   done = true;
644 }  
645
646 /*************************************************************************
647  * The CCacheEntry here is mostly a wrapper around the "memory"
648  * cacheentry provided by shibboleth.  The only difference is that we
649  * intercept the isSessionValid() so that we can "touch()" the
650  * database if the session is still valid.
651  */
652
653 ShibMySQLCCacheEntry::ShibMySQLCCacheEntry(const char* key, ISessionCacheEntry* entry, ShibMySQLCCache* cache)
654 {
655   m_cacheEntry = entry;
656   m_key = key;
657   m_cache = cache;
658 }
659
660 bool ShibMySQLCCacheEntry::isValid(time_t lifetime, time_t timeout) const
661 {
662   bool res = m_cacheEntry->isValid(lifetime, timeout);
663   if (res == true)
664     res = touch();
665   return res;
666 }
667
668 bool ShibMySQLCCacheEntry::touch() const
669 {
670   string q=string("UPDATE state SET atime=NOW() WHERE cookie='") + m_key + "'";
671
672   MYSQL* mysql = m_cache->getMYSQL();
673   if (mysql_query(mysql, q.c_str())) {
674     m_cache->log->info("Error updating timestamp on %s: %s",
675                         m_key.c_str(), mysql_error(mysql));
676     return false;
677   }
678   return true;
679 }
680
681 /*************************************************************************
682  * The registration functions here...
683  */
684
685 IPlugIn* new_mysql_ccache(const DOMElement* e)
686 {
687   return new ShibMySQLCCache(e);
688 }
689
690 IPlugIn* new_mysql_replay(const DOMElement* e)
691 {
692   return NULL;
693 }
694
695 #define REPLAYPLUGINTYPE "edu.internet2.middleware.shibboleth.sp.provider.MySQLReplayCacheProvider"
696 #define SESSIONPLUGINTYPE "edu.internet2.middleware.shibboleth.sp.provider.MySQLSessionCacheProvider"
697
698 extern "C" int SHIBMYSQL_EXPORTS saml_extension_init(void*)
699 {
700   // register this ccache type
701   SAMLConfig::getConfig().getPlugMgr().regFactory(REPLAYPLUGINTYPE, &new_mysql_replay);
702   SAMLConfig::getConfig().getPlugMgr().regFactory(SESSIONPLUGINTYPE, &new_mysql_ccache);
703   return 0;
704 }
705
706 extern "C" void SHIBMYSQL_EXPORTS saml_extension_term()
707 {
708   SAMLConfig::getConfig().getPlugMgr().unregFactory(REPLAYPLUGINTYPE);
709   SAMLConfig::getConfig().getPlugMgr().unregFactory(SESSIONPLUGINTYPE);
710 }
711
712 /*************************************************************************
713  * Local Functions
714  */
715
716 extern "C" void shib_mysql_destroy_handle(void* data)
717 {
718   MYSQL* mysql = (MYSQL*) data;
719   mysql_close(mysql);
720 }