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 * odbc-ccache.cpp - Shibboleth Credential Cache using ODBC
20 * Scott Cantor <cantor.2@osu.edu>
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 _CRT_NONSTDC_NO_DEPRECATE 1
34 # define _CRT_SECURE_NO_DEPRECATE 1
36 # define SHIBODBC_EXPORTS __declspec(dllexport)
38 # define SHIBODBC_EXPORTS
41 #include <shib-target/shib-target.h>
42 #include <shibsp/exceptions.h>
43 #include <log4cpp/Category.hh>
44 #include <xmltooling/util/NDC.h>
45 #include <xmltooling/util/Threads.h>
54 #ifdef HAVE_LIBDMALLOCXX
58 using namespace shibsp;
59 using namespace shibtarget;
60 using namespace opensaml::saml2md;
62 using namespace xmltooling;
63 using namespace log4cpp;
66 #define PLUGIN_VER_MAJOR 3
67 #define PLUGIN_VER_MINOR 0
69 #define COLSIZE_KEY 64
70 #define COLSIZE_APPLICATION_ID 256
71 #define COLSIZE_ADDRESS 128
72 #define COLSIZE_PROVIDER_ID 256
73 #define LONGDATA_BUFLEN 32768
77 cookie VARCHAR(64) PRIMARY KEY,
78 application_id VARCHAR(256),
84 provider VARCHAR(256),
91 #define REPLAY_TABLE \
92 "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
93 "expires TIMESTAMP, " \
96 static const XMLCh ConnectionString[] =
97 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
98 chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
100 static const XMLCh cleanupInterval[] =
101 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
102 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
104 static const XMLCh cacheTimeout[] =
105 { 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 };
106 static const XMLCh odbcTimeout[] =
107 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
108 static const XMLCh storeAttributes[] =
109 { 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 };
112 ODBCConn(SQLHDBC conn) : handle(conn) {}
113 ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
114 operator SQLHDBC() {return handle;}
118 class ODBCBase : public virtual saml::IPlugIn
121 ODBCBase(const DOMElement* e);
129 const DOMElement* m_root; // can only use this during initialization
132 static SQLHENV m_henv; // single handle for both plugins
133 bool m_bInitializedODBC; // tracks which class handled the process
134 static const char* p_connstring;
136 pair<int,int> getVersion(SQLHDBC);
137 void log_error(SQLHANDLE handle, SQLSMALLINT htype);
140 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
141 const char* ODBCBase::p_connstring = NULL;
143 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
146 xmltooling::NDC ndc("ODBCBase");
148 log = &(Category::getInstance("shibtarget.ODBC"));
150 if (m_henv == SQL_NULL_HANDLE) {
151 // Enable connection pooling.
152 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
154 // Allocate the environment.
155 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
156 throw ConfigurationException("ODBC failed to initialize.");
159 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
161 log->info("ODBC initialized");
162 m_bInitializedODBC = true;
165 // Grab connection string from the configuration.
166 e=XMLHelper::getFirstChildElement(e,ConnectionString);
167 if (!e || !e->hasChildNodes()) {
170 throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
172 m_connstring=p_connstring;
175 xmltooling::auto_ptr_char arg(e->getFirstChild()->getNodeValue());
176 m_connstring=arg.get();
177 p_connstring=m_connstring.c_str();
180 // Connect and check version.
181 SQLHDBC conn=getHDBC();
182 pair<int,int> v=getVersion(conn);
183 SQLFreeHandle(SQL_HANDLE_DBC,conn);
185 // Make sure we've got the right version.
186 if (v.first != PLUGIN_VER_MAJOR) {
188 log->crit("unknown database version: %d.%d", v.first, v.second);
189 throw SAMLException("Unknown cache database version.");
193 ODBCBase::~ODBCBase()
196 if (m_bInitializedODBC)
197 SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
198 m_bInitializedODBC=false;
199 m_henv = SQL_NULL_HANDLE;
203 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
213 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
214 if (SQL_SUCCEEDED(ret))
215 log->error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
216 } while(SQL_SUCCEEDED(ret));
219 SQLHDBC ODBCBase::getHDBC()
222 xmltooling::NDC ndc("getMYSQL");
227 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
228 if (!SQL_SUCCEEDED(sr)) {
229 log->error("failed to allocate connection handle");
230 log_error(m_henv, SQL_HANDLE_ENV);
231 throw SAMLException("ODBCBase::getHDBC failed to allocate connection handle");
234 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
235 if (!SQL_SUCCEEDED(sr)) {
236 log->error("failed to connect to database");
237 log_error(handle, SQL_HANDLE_DBC);
238 throw SAMLException("ODBCBase::getHDBC failed to connect to database");
244 pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
246 // Grab the version number from the database.
248 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
250 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
251 if (!SQL_SUCCEEDED(sr)) {
252 log->error("failed to read version from database");
253 log_error(hstmt, SQL_HANDLE_STMT);
254 throw SAMLException("ODBCBase::getVersion failed to read version from database");
259 SQLBindCol(hstmt,1,SQL_C_SLONG,&major,0,NULL);
260 SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
262 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
263 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
264 return pair<int,int>(major,minor);
267 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
268 log->error("no rows returned in version query");
269 throw SAMLException("ODBCBase::getVersion failed to read version from database");
272 class ODBCCCache : public ODBCBase, virtual public ISessionCache, virtual public ISessionCacheStore
275 ODBCCCache(const DOMElement* e);
276 virtual ~ODBCCCache();
278 // Delegate all the ISessionCache methods.
280 const IApplication* application,
281 const RoleDescriptor* role,
282 const char* client_addr,
283 const SAMLSubject* subject,
284 const char* authnContext,
285 const SAMLResponse* tokens
287 { return m_cache->insert(application,role,client_addr,subject,authnContext,tokens); }
288 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
289 { return m_cache->find(key,application,client_addr); }
290 void remove(const char* key, const IApplication* application, const char* client_addr)
291 { m_cache->remove(key,application,client_addr); }
293 bool setBackingStore(ISessionCacheStore*) {return false;}
295 // Store methods handle the database work
298 const IApplication* application,
299 const ISessionCacheEntry* entry,
306 string& applicationId,
307 string& clientAddress,
310 string& authnContext,
317 HRESULT onRead(const char* key, time_t& accessed);
318 HRESULT onRead(const char* key, string& tokens);
319 HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
320 HRESULT onDelete(const char* key);
325 bool m_storeAttributes;
326 ISessionCache* m_cache;
327 xmltooling::CondWait* shutdown_wait;
329 xmltooling::Thread* cleanup_thread;
331 static void* cleanup_fcn(void*); // XXX Assumed an ODBCCCache
334 ODBCCCache::ODBCCCache(const DOMElement* e) : ODBCBase(e), m_storeAttributes(false)
337 xmltooling::NDC ndc("ODBCCCache");
339 log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
341 m_cache = dynamic_cast<ISessionCache*>(
342 SAMLConfig::getConfig().getPlugMgr().newPlugin(MEMORY_SESSIONCACHE, m_root)
344 if (!m_cache->setBackingStore(this)) {
346 throw SAMLException("Unable to register ODBC cache plugin as a cache store.");
349 shutdown_wait = CondWait::create();
352 // Load our configuration details...
353 const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
354 if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
355 m_storeAttributes=true;
357 // Initialize the cleanup thread
358 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
361 ODBCCCache::~ODBCCCache()
364 shutdown_wait->signal();
365 cleanup_thread->join(NULL);
369 void appendXML(ostream& os, const char* str)
371 const char* pos=strchr(str,'\'');
373 os.write(str,pos-str);
376 pos=strchr(str,'\'');
381 HRESULT ODBCCCache::onCreate(
383 const IApplication* application,
384 const ISessionCacheEntry* entry,
391 xmltooling::NDC ndc("onCreate");
394 // Get XML data from entry. Default is not to return SAML objects.
395 const char* context=entry->getAuthnContext();
396 pair<const char*,const SAMLSubject*> subject=entry->getSubject();
397 pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
399 // Stringify timestamp.
402 #ifndef HAVE_GMTIME_R
403 struct tm* ptime=gmtime(&created);
406 struct tm* ptime=gmtime_r(&created,&res);
409 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
411 // Prepare insert statement.
413 q << "INSERT state VALUES ('" << key << "','" << application->getId() << "'," << timebuf << "," << timebuf
414 << ",'" << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId()
416 appendXML(q,subject.first);
418 appendXML(q,context);
420 if (m_storeAttributes && tokens.first) {
422 appendXML(q,tokens.first);
427 if (log->isDebugEnabled())
428 log->debug("SQL insert: %s", q.str().c_str());
430 // Get statement handle.
432 ODBCConn conn(getHDBC());
433 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
435 // Execute statement.
437 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
438 if (!SQL_SUCCEEDED(sr)) {
439 log->error("failed to insert record into database");
440 log_error(hstmt, SQL_HANDLE_STMT);
444 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
448 HRESULT ODBCCCache::onRead(
450 string& applicationId,
451 string& clientAddress,
454 string& authnContext,
463 xmltooling::NDC ndc("onRead");
466 log->debug("searching database...");
469 ODBCConn conn(getHDBC());
470 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
472 string q = string("SELECT application_id,ctime,atime,addr,major,minor,provider,subject,authn_context,tokens FROM state WHERE cookie='") + key + "'";
473 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
474 if (!SQL_SUCCEEDED(sr)) {
475 log->error("error searching for (%s)",key);
476 log_error(hstmt, SQL_HANDLE_STMT);
477 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
481 SQLINTEGER major,minor;
482 SQL_TIMESTAMP_STRUCT atime,ctime;
483 SQLCHAR application_id[COLSIZE_APPLICATION_ID+1];
484 SQLCHAR addr[COLSIZE_ADDRESS+1];
485 SQLCHAR provider_id[COLSIZE_PROVIDER_ID+1];
487 // Bind simple output columns.
488 SQLBindCol(hstmt,1,SQL_C_CHAR,application_id,sizeof(application_id),NULL);
489 SQLBindCol(hstmt,2,SQL_C_TYPE_TIMESTAMP,&ctime,0,NULL);
490 SQLBindCol(hstmt,3,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
491 SQLBindCol(hstmt,4,SQL_C_CHAR,addr,sizeof(addr),NULL);
492 SQLBindCol(hstmt,5,SQL_C_SLONG,&major,0,NULL);
493 SQLBindCol(hstmt,6,SQL_C_SLONG,&minor,0,NULL);
494 SQLBindCol(hstmt,7,SQL_C_CHAR,provider_id,sizeof(provider_id),NULL);
496 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
497 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
501 log->debug("session found, tranfering data back into memory");
503 // Copy back simple data.
504 applicationId = (char*)application_id;
505 clientAddress = (char*)addr;
506 majorVersion = major;
507 minorVersion = minor;
508 providerId = (char*)provider_id;
511 t.tm_sec=ctime.second;
512 t.tm_min=ctime.minute;
513 t.tm_hour=ctime.hour;
515 t.tm_mon=ctime.month-1;
516 t.tm_year=ctime.year-1900;
518 #if defined(HAVE_TIMEGM)
521 // Windows, and hopefully most others...?
522 created = mktime(&t) - timezone;
524 t.tm_sec=atime.second;
525 t.tm_min=atime.minute;
526 t.tm_hour=atime.hour;
528 t.tm_mon=atime.month-1;
529 t.tm_year=atime.year-1900;
531 #if defined(HAVE_TIMEGM)
534 // Windows, and hopefully most others...?
535 accessed = mktime(&t) - timezone;
538 // Extract text data.
539 string* ptrs[] = {&subject, &authnContext, &tokens};
542 SQLCHAR buf[LONGDATA_BUFLEN];
543 for (int i=0; i<3; i++) {
544 while ((sr=SQLGetData(hstmt,i+8,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
545 if (!SUCCEEDED(sr)) {
546 log->error("error while reading text field from result set");
547 log_error(hstmt, SQL_HANDLE_STMT);
551 ptrs[i]->append((char*)buf);
555 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
559 HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
562 xmltooling::NDC ndc("onRead");
565 log->debug("reading last access time from database");
568 ODBCConn conn(getHDBC());
569 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
571 string q = string("SELECT atime FROM state WHERE cookie='") + key + "'";
572 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
573 if (!SQL_SUCCEEDED(sr)) {
574 log->error("error searching for (%s)",key);
575 log_error(hstmt, SQL_HANDLE_STMT);
576 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
580 SQL_TIMESTAMP_STRUCT atime;
581 SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
583 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
584 log->warn("session expected, but not found in database");
585 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
589 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
592 t.tm_sec=atime.second;
593 t.tm_min=atime.minute;
594 t.tm_hour=atime.hour;
596 t.tm_mon=atime.month-1;
597 t.tm_year=atime.year-1900;
599 #if defined(HAVE_TIMEGM)
602 // Windows, and hopefully most others...?
603 accessed = mktime(&t) - timezone;
608 HRESULT ODBCCCache::onRead(const char* key, string& tokens)
611 xmltooling::NDC ndc("onRead");
614 if (!m_storeAttributes)
617 log->debug("reading cached tokens from database");
620 ODBCConn conn(getHDBC());
621 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
623 string q = string("SELECT tokens FROM state WHERE cookie='") + key + "'";
624 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
625 if (!SQL_SUCCEEDED(sr)) {
626 log->error("error searching for (%s)",key);
627 log_error(hstmt, SQL_HANDLE_STMT);
628 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
632 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
633 log->warn("session expected, but not found in database");
634 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
640 SQLCHAR buf[LONGDATA_BUFLEN];
641 while ((sr=SQLGetData(hstmt,1,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
642 if (!SUCCEEDED(sr)) {
643 log->error("error while reading text field from result set");
644 log_error(hstmt, SQL_HANDLE_STMT);
648 tokens += (char*)buf;
651 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
655 HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
658 xmltooling::NDC ndc("onUpdate");
664 #ifndef HAVE_GMTIME_R
665 struct tm* ptime=gmtime(&lastAccess);
668 struct tm* ptime=gmtime_r(&lastAccess,&res);
671 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
673 q << "UPDATE state SET atime=" << timebuf << " WHERE cookie='" << key << "'";
676 if (!m_storeAttributes)
678 q << "UPDATE state SET tokens=";
686 q << "WHERE cookie='" << key << "'";
689 log->warn("onUpdate called with nothing to do!");
695 ODBCConn conn(getHDBC());
696 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
697 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
700 else if (!SQL_SUCCEEDED(sr)) {
701 log->error("error updating record (key=%s)", key);
702 log_error(hstmt, SQL_HANDLE_STMT);
706 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
710 HRESULT ODBCCCache::onDelete(const char* key)
713 xmltooling::NDC ndc("onDelete");
717 ODBCConn conn(getHDBC());
718 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
719 string q = string("DELETE FROM state WHERE cookie='") + key + "'";
720 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
725 else if (!SQL_SUCCEEDED(sr)) {
726 log->error("error deleting record (key=%s)", key);
727 log_error(hstmt, SQL_HANDLE_STMT);
731 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
735 void ODBCCCache::cleanup()
738 xmltooling::NDC ndc("cleanup");
741 Mutex* mutex = xmltooling::Mutex::create();
744 int timeout_life = 0;
746 // Load our configuration details...
747 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
749 rerun_timer = XMLString::parseInt(tag);
751 // search for 'mysql-cache-timeout' and then the regular cache timeout
752 tag=m_root->getAttributeNS(NULL,odbcTimeout);
754 timeout_life = XMLString::parseInt(tag);
756 tag=m_root->getAttributeNS(NULL,cacheTimeout);
758 timeout_life = XMLString::parseInt(tag);
761 if (rerun_timer <= 0)
762 rerun_timer = 300; // rerun every 5 minutes
764 if (timeout_life <= 0)
765 timeout_life = 28800; // timeout after 8 hours
769 log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
771 while (shutdown == false) {
772 shutdown_wait->timedwait(mutex, rerun_timer);
774 if (shutdown == true)
777 // Find all the entries in the database that haven't been used
778 // recently In particular, find all entries that have not been
779 // accessed in 'timeout_life' seconds.
781 time_t stale=time(NULL)-timeout_life;
782 #ifndef HAVE_GMTIME_R
783 struct tm* ptime=gmtime(&stale);
786 struct tm* ptime=gmtime_r(&stale,&res);
789 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
791 string q = string("DELETE state WHERE atime < ") + timebuf;
794 ODBCConn conn(getHDBC());
795 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
796 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
797 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
798 log->error("error purging old records");
799 log_error(hstmt, SQL_HANDLE_STMT);
802 SQLINTEGER rowcount=0;
803 sr=SQLRowCount(hstmt,&rowcount);
804 if (SQL_SUCCEEDED(sr) && rowcount > 0)
805 log->info("purging %d old sessions",rowcount);
807 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
810 log->info("cleanup thread exiting...");
814 xmltooling::Thread::exit(NULL);
817 void* ODBCCCache::cleanup_fcn(void* cache_p)
819 ODBCCCache* cache = (ODBCCCache*)cache_p;
822 // First, let's block all signals
823 Thread::mask_all_signals();
826 // Now run the cleanup process.
832 class ODBCReplayCache : public ODBCBase, virtual public IReplayCache
835 ODBCReplayCache(const DOMElement* e);
836 virtual ~ODBCReplayCache() {}
838 bool check(const XMLCh* str, time_t expires) {xmltooling::auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
839 bool check(const char* str, time_t expires);
842 ODBCReplayCache::ODBCReplayCache(const DOMElement* e) : ODBCBase(e)
845 saml::NDC ndc("ODBCReplayCache");
847 log = &(Category::getInstance("shibtarget.ReplayCache.ODBC"));
850 bool ODBCReplayCache::check(const char* str, time_t expires)
853 saml::NDC ndc("check");
856 time_t now=time(NULL);
857 #ifndef HAVE_GMTIME_R
858 struct tm* ptime=gmtime(&now);
861 struct tm* ptime=gmtime_r(&now,&res);
864 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
866 // Remove expired entries.
868 ODBCConn conn(getHDBC());
869 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
870 string q = string("DELETE FROM replay WHERE expires < ") + timebuf;
871 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
872 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
873 log->error("error purging old replay cache entries");
874 log_error(hstmt, SQL_HANDLE_STMT);
876 SQLCloseCursor(hstmt);
878 // Look for a replay.
879 q = string("SELECT id FROM replay WHERE id='") + str + "'";
880 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
881 if (!SQL_SUCCEEDED(sr)) {
882 log->error("error searching replay cache");
883 log_error(hstmt, SQL_HANDLE_STMT);
884 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
885 throw SAMLException("Replay cache failed, please inform application support staff.");
888 // If we got a record, we return false.
889 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
890 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
893 SQLCloseCursor(hstmt);
895 #ifndef HAVE_GMTIME_R
896 ptime=gmtime(&expires);
898 ptime=gmtime_r(&expires,&res);
900 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
902 // Add it to the database.
903 q = string("INSERT replay VALUES('") + str + "'," + timebuf + ")";
904 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
905 if (!SQL_SUCCEEDED(sr)) {
906 log->error("error inserting replay cache entry", str);
907 log_error(hstmt, SQL_HANDLE_STMT);
908 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
909 throw SAMLException("Replay cache failed, please inform application support staff.");
912 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
919 IPlugIn* new_odbc_ccache(const DOMElement* e)
921 return new ODBCCCache(e);
924 IPlugIn* new_odbc_replay(const DOMElement* e)
926 return new ODBCReplayCache(e);
930 extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
932 // register this ccache type
933 SAMLConfig::getConfig().getPlugMgr().regFactory(ODBC_REPLAYCACHE, &new_odbc_replay);
934 SAMLConfig::getConfig().getPlugMgr().regFactory(ODBC_SESSIONCACHE, &new_odbc_ccache);
938 extern "C" void SHIBODBC_EXPORTS saml_extension_term()
940 SAMLConfig::getConfig().getPlugMgr().unregFactory(ODBC_SESSIONCACHE);
941 SAMLConfig::getConfig().getPlugMgr().unregFactory(ODBC_REPLAYCACHE);