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