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 <shibsp/SPConfig.h>
44 #include <log4cpp/Category.hh>
45 #include <xmltooling/util/NDC.h>
46 #include <xmltooling/util/Threads.h>
55 #ifdef HAVE_LIBDMALLOCXX
59 using namespace shibsp;
60 using namespace shibtarget;
61 using namespace opensaml::saml2md;
63 using namespace xmltooling;
64 using namespace log4cpp;
67 #define PLUGIN_VER_MAJOR 3
68 #define PLUGIN_VER_MINOR 0
70 #define COLSIZE_KEY 64
71 #define COLSIZE_APPLICATION_ID 256
72 #define COLSIZE_ADDRESS 128
73 #define COLSIZE_PROVIDER_ID 256
74 #define LONGDATA_BUFLEN 32768
78 cookie VARCHAR(64) PRIMARY KEY,
79 application_id VARCHAR(256),
85 provider VARCHAR(256),
92 #define REPLAY_TABLE \
93 "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
94 "expires TIMESTAMP, " \
97 static const XMLCh ConnectionString[] =
98 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
99 chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
101 static const XMLCh cleanupInterval[] =
102 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
103 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
105 static const XMLCh cacheTimeout[] =
106 { 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 };
107 static const XMLCh odbcTimeout[] =
108 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
109 static const XMLCh storeAttributes[] =
110 { 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 };
113 ODBCConn(SQLHDBC conn) : handle(conn) {}
114 ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
115 operator SQLHDBC() {return handle;}
119 class ODBCBase : public virtual saml::IPlugIn
122 ODBCBase(const DOMElement* e);
130 const DOMElement* m_root; // can only use this during initialization
133 static SQLHENV m_henv; // single handle for both plugins
134 bool m_bInitializedODBC; // tracks which class handled the process
135 static const char* p_connstring;
137 pair<int,int> getVersion(SQLHDBC);
138 void log_error(SQLHANDLE handle, SQLSMALLINT htype);
141 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
142 const char* ODBCBase::p_connstring = NULL;
144 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
147 xmltooling::NDC ndc("ODBCBase");
149 log = &(Category::getInstance("shibtarget.ODBC"));
151 if (m_henv == SQL_NULL_HANDLE) {
152 // Enable connection pooling.
153 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
155 // Allocate the environment.
156 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
157 throw ConfigurationException("ODBC failed to initialize.");
160 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
162 log->info("ODBC initialized");
163 m_bInitializedODBC = true;
166 // Grab connection string from the configuration.
167 e=XMLHelper::getFirstChildElement(e,ConnectionString);
168 if (!e || !e->hasChildNodes()) {
171 throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
173 m_connstring=p_connstring;
176 xmltooling::auto_ptr_char arg(e->getFirstChild()->getNodeValue());
177 m_connstring=arg.get();
178 p_connstring=m_connstring.c_str();
181 // Connect and check version.
182 SQLHDBC conn=getHDBC();
183 pair<int,int> v=getVersion(conn);
184 SQLFreeHandle(SQL_HANDLE_DBC,conn);
186 // Make sure we've got the right version.
187 if (v.first != PLUGIN_VER_MAJOR) {
189 log->crit("unknown database version: %d.%d", v.first, v.second);
190 throw SAMLException("Unknown cache database version.");
194 ODBCBase::~ODBCBase()
197 if (m_bInitializedODBC)
198 SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
199 m_bInitializedODBC=false;
200 m_henv = SQL_NULL_HANDLE;
204 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
214 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
215 if (SQL_SUCCEEDED(ret))
216 log->error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
217 } while(SQL_SUCCEEDED(ret));
220 SQLHDBC ODBCBase::getHDBC()
223 xmltooling::NDC ndc("getMYSQL");
228 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
229 if (!SQL_SUCCEEDED(sr)) {
230 log->error("failed to allocate connection handle");
231 log_error(m_henv, SQL_HANDLE_ENV);
232 throw SAMLException("ODBCBase::getHDBC failed to allocate connection handle");
235 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
236 if (!SQL_SUCCEEDED(sr)) {
237 log->error("failed to connect to database");
238 log_error(handle, SQL_HANDLE_DBC);
239 throw SAMLException("ODBCBase::getHDBC failed to connect to database");
245 pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
247 // Grab the version number from the database.
249 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
251 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
252 if (!SQL_SUCCEEDED(sr)) {
253 log->error("failed to read version from database");
254 log_error(hstmt, SQL_HANDLE_STMT);
255 throw SAMLException("ODBCBase::getVersion failed to read version from database");
260 SQLBindCol(hstmt,1,SQL_C_SLONG,&major,0,NULL);
261 SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
263 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
264 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
265 return pair<int,int>(major,minor);
268 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
269 log->error("no rows returned in version query");
270 throw SAMLException("ODBCBase::getVersion failed to read version from database");
273 class ODBCCCache : public ODBCBase, virtual public ISessionCache, virtual public ISessionCacheStore
276 ODBCCCache(const DOMElement* e);
277 virtual ~ODBCCCache();
279 // Delegate all the ISessionCache methods.
281 const IApplication* application,
282 const RoleDescriptor* role,
283 const char* client_addr,
284 const SAMLSubject* subject,
285 const char* authnContext,
286 const SAMLResponse* tokens
288 { return m_cache->insert(application,role,client_addr,subject,authnContext,tokens); }
289 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
290 { return m_cache->find(key,application,client_addr); }
291 void remove(const char* key, const IApplication* application, const char* client_addr)
292 { m_cache->remove(key,application,client_addr); }
294 bool setBackingStore(ISessionCacheStore*) {return false;}
296 // Store methods handle the database work
299 const IApplication* application,
300 const ISessionCacheEntry* entry,
307 string& applicationId,
308 string& clientAddress,
311 string& authnContext,
318 HRESULT onRead(const char* key, time_t& accessed);
319 HRESULT onRead(const char* key, string& tokens);
320 HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
321 HRESULT onDelete(const char* key);
326 bool m_storeAttributes;
327 ISessionCache* m_cache;
328 xmltooling::CondWait* shutdown_wait;
330 xmltooling::Thread* cleanup_thread;
332 static void* cleanup_fcn(void*); // XXX Assumed an ODBCCCache
335 ODBCCCache::ODBCCCache(const DOMElement* e) : ODBCBase(e), m_storeAttributes(false)
338 xmltooling::NDC ndc("ODBCCCache");
340 log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
342 m_cache = dynamic_cast<ISessionCache*>(
343 SAMLConfig::getConfig().getPlugMgr().newPlugin(MEMORY_SESSIONCACHE, m_root)
345 if (!m_cache->setBackingStore(this)) {
347 throw SAMLException("Unable to register ODBC cache plugin as a cache store.");
350 shutdown_wait = CondWait::create();
353 // Load our configuration details...
354 const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
355 if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
356 m_storeAttributes=true;
358 // Initialize the cleanup thread
359 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
362 ODBCCCache::~ODBCCCache()
365 shutdown_wait->signal();
366 cleanup_thread->join(NULL);
370 void appendXML(ostream& os, const char* str)
372 const char* pos=strchr(str,'\'');
374 os.write(str,pos-str);
377 pos=strchr(str,'\'');
382 HRESULT ODBCCCache::onCreate(
384 const IApplication* application,
385 const ISessionCacheEntry* entry,
392 xmltooling::NDC ndc("onCreate");
395 // Get XML data from entry. Default is not to return SAML objects.
396 const char* context=entry->getAuthnContext();
397 pair<const char*,const SAMLSubject*> subject=entry->getSubject();
398 pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
400 // Stringify timestamp.
403 #ifndef HAVE_GMTIME_R
404 struct tm* ptime=gmtime(&created);
407 struct tm* ptime=gmtime_r(&created,&res);
410 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
412 // Prepare insert statement.
414 q << "INSERT state VALUES ('" << key << "','" << application->getId() << "'," << timebuf << "," << timebuf
415 << ",'" << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId()
417 appendXML(q,subject.first);
419 appendXML(q,context);
421 if (m_storeAttributes && tokens.first) {
423 appendXML(q,tokens.first);
428 if (log->isDebugEnabled())
429 log->debug("SQL insert: %s", q.str().c_str());
431 // Get statement handle.
433 ODBCConn conn(getHDBC());
434 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
436 // Execute statement.
438 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
439 if (!SQL_SUCCEEDED(sr)) {
440 log->error("failed to insert record into database");
441 log_error(hstmt, SQL_HANDLE_STMT);
445 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
449 HRESULT ODBCCCache::onRead(
451 string& applicationId,
452 string& clientAddress,
455 string& authnContext,
464 xmltooling::NDC ndc("onRead");
467 log->debug("searching database...");
470 ODBCConn conn(getHDBC());
471 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
473 string q = string("SELECT application_id,ctime,atime,addr,major,minor,provider,subject,authn_context,tokens FROM state WHERE cookie='") + key + "'";
474 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
475 if (!SQL_SUCCEEDED(sr)) {
476 log->error("error searching for (%s)",key);
477 log_error(hstmt, SQL_HANDLE_STMT);
478 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
482 SQLINTEGER major,minor;
483 SQL_TIMESTAMP_STRUCT atime,ctime;
484 SQLCHAR application_id[COLSIZE_APPLICATION_ID+1];
485 SQLCHAR addr[COLSIZE_ADDRESS+1];
486 SQLCHAR provider_id[COLSIZE_PROVIDER_ID+1];
488 // Bind simple output columns.
489 SQLBindCol(hstmt,1,SQL_C_CHAR,application_id,sizeof(application_id),NULL);
490 SQLBindCol(hstmt,2,SQL_C_TYPE_TIMESTAMP,&ctime,0,NULL);
491 SQLBindCol(hstmt,3,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
492 SQLBindCol(hstmt,4,SQL_C_CHAR,addr,sizeof(addr),NULL);
493 SQLBindCol(hstmt,5,SQL_C_SLONG,&major,0,NULL);
494 SQLBindCol(hstmt,6,SQL_C_SLONG,&minor,0,NULL);
495 SQLBindCol(hstmt,7,SQL_C_CHAR,provider_id,sizeof(provider_id),NULL);
497 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
498 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
502 log->debug("session found, tranfering data back into memory");
504 // Copy back simple data.
505 applicationId = (char*)application_id;
506 clientAddress = (char*)addr;
507 majorVersion = major;
508 minorVersion = minor;
509 providerId = (char*)provider_id;
512 t.tm_sec=ctime.second;
513 t.tm_min=ctime.minute;
514 t.tm_hour=ctime.hour;
516 t.tm_mon=ctime.month-1;
517 t.tm_year=ctime.year-1900;
519 #if defined(HAVE_TIMEGM)
522 // Windows, and hopefully most others...?
523 created = mktime(&t) - timezone;
525 t.tm_sec=atime.second;
526 t.tm_min=atime.minute;
527 t.tm_hour=atime.hour;
529 t.tm_mon=atime.month-1;
530 t.tm_year=atime.year-1900;
532 #if defined(HAVE_TIMEGM)
535 // Windows, and hopefully most others...?
536 accessed = mktime(&t) - timezone;
539 // Extract text data.
540 string* ptrs[] = {&subject, &authnContext, &tokens};
543 SQLCHAR buf[LONGDATA_BUFLEN];
544 for (int i=0; i<3; i++) {
545 while ((sr=SQLGetData(hstmt,i+8,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
546 if (!SUCCEEDED(sr)) {
547 log->error("error while reading text field from result set");
548 log_error(hstmt, SQL_HANDLE_STMT);
552 ptrs[i]->append((char*)buf);
556 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
560 HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
563 xmltooling::NDC ndc("onRead");
566 log->debug("reading last access time from database");
569 ODBCConn conn(getHDBC());
570 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
572 string q = string("SELECT atime FROM state WHERE cookie='") + key + "'";
573 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
574 if (!SQL_SUCCEEDED(sr)) {
575 log->error("error searching for (%s)",key);
576 log_error(hstmt, SQL_HANDLE_STMT);
577 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
581 SQL_TIMESTAMP_STRUCT atime;
582 SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
584 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
585 log->warn("session expected, but not found in database");
586 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
590 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
593 t.tm_sec=atime.second;
594 t.tm_min=atime.minute;
595 t.tm_hour=atime.hour;
597 t.tm_mon=atime.month-1;
598 t.tm_year=atime.year-1900;
600 #if defined(HAVE_TIMEGM)
603 // Windows, and hopefully most others...?
604 accessed = mktime(&t) - timezone;
609 HRESULT ODBCCCache::onRead(const char* key, string& tokens)
612 xmltooling::NDC ndc("onRead");
615 if (!m_storeAttributes)
618 log->debug("reading cached tokens from database");
621 ODBCConn conn(getHDBC());
622 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
624 string q = string("SELECT tokens FROM state WHERE cookie='") + key + "'";
625 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
626 if (!SQL_SUCCEEDED(sr)) {
627 log->error("error searching for (%s)",key);
628 log_error(hstmt, SQL_HANDLE_STMT);
629 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
633 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
634 log->warn("session expected, but not found in database");
635 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
641 SQLCHAR buf[LONGDATA_BUFLEN];
642 while ((sr=SQLGetData(hstmt,1,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
643 if (!SUCCEEDED(sr)) {
644 log->error("error while reading text field from result set");
645 log_error(hstmt, SQL_HANDLE_STMT);
649 tokens += (char*)buf;
652 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
656 HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
659 xmltooling::NDC ndc("onUpdate");
665 #ifndef HAVE_GMTIME_R
666 struct tm* ptime=gmtime(&lastAccess);
669 struct tm* ptime=gmtime_r(&lastAccess,&res);
672 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
674 q << "UPDATE state SET atime=" << timebuf << " WHERE cookie='" << key << "'";
677 if (!m_storeAttributes)
679 q << "UPDATE state SET tokens=";
687 q << "WHERE cookie='" << key << "'";
690 log->warn("onUpdate called with nothing to do!");
696 ODBCConn conn(getHDBC());
697 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
698 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
701 else if (!SQL_SUCCEEDED(sr)) {
702 log->error("error updating record (key=%s)", key);
703 log_error(hstmt, SQL_HANDLE_STMT);
707 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
711 HRESULT ODBCCCache::onDelete(const char* key)
714 xmltooling::NDC ndc("onDelete");
718 ODBCConn conn(getHDBC());
719 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
720 string q = string("DELETE FROM state WHERE cookie='") + key + "'";
721 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
726 else if (!SQL_SUCCEEDED(sr)) {
727 log->error("error deleting record (key=%s)", key);
728 log_error(hstmt, SQL_HANDLE_STMT);
732 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
736 void ODBCCCache::cleanup()
739 xmltooling::NDC ndc("cleanup");
742 Mutex* mutex = xmltooling::Mutex::create();
745 int timeout_life = 0;
747 // Load our configuration details...
748 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
750 rerun_timer = XMLString::parseInt(tag);
752 // search for 'mysql-cache-timeout' and then the regular cache timeout
753 tag=m_root->getAttributeNS(NULL,odbcTimeout);
755 timeout_life = XMLString::parseInt(tag);
757 tag=m_root->getAttributeNS(NULL,cacheTimeout);
759 timeout_life = XMLString::parseInt(tag);
762 if (rerun_timer <= 0)
763 rerun_timer = 300; // rerun every 5 minutes
765 if (timeout_life <= 0)
766 timeout_life = 28800; // timeout after 8 hours
770 log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
772 while (shutdown == false) {
773 shutdown_wait->timedwait(mutex, rerun_timer);
775 if (shutdown == true)
778 // Find all the entries in the database that haven't been used
779 // recently In particular, find all entries that have not been
780 // accessed in 'timeout_life' seconds.
782 time_t stale=time(NULL)-timeout_life;
783 #ifndef HAVE_GMTIME_R
784 struct tm* ptime=gmtime(&stale);
787 struct tm* ptime=gmtime_r(&stale,&res);
790 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
792 string q = string("DELETE state WHERE atime < ") + timebuf;
795 ODBCConn conn(getHDBC());
796 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
797 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
798 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
799 log->error("error purging old records");
800 log_error(hstmt, SQL_HANDLE_STMT);
803 SQLINTEGER rowcount=0;
804 sr=SQLRowCount(hstmt,&rowcount);
805 if (SQL_SUCCEEDED(sr) && rowcount > 0)
806 log->info("purging %d old sessions",rowcount);
808 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
811 log->info("cleanup thread exiting...");
815 xmltooling::Thread::exit(NULL);
818 void* ODBCCCache::cleanup_fcn(void* cache_p)
820 ODBCCCache* cache = (ODBCCCache*)cache_p;
823 // First, let's block all signals
824 Thread::mask_all_signals();
827 // Now run the cleanup process.
833 class ODBCReplayCache : public ODBCBase, virtual public IReplayCache
836 ODBCReplayCache(const DOMElement* e);
837 virtual ~ODBCReplayCache() {}
839 bool check(const XMLCh* str, time_t expires) {xmltooling::auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
840 bool check(const char* str, time_t expires);
843 ODBCReplayCache::ODBCReplayCache(const DOMElement* e) : ODBCBase(e)
846 saml::NDC ndc("ODBCReplayCache");
848 log = &(Category::getInstance("shibtarget.ReplayCache.ODBC"));
851 bool ODBCReplayCache::check(const char* str, time_t expires)
854 saml::NDC ndc("check");
857 time_t now=time(NULL);
858 #ifndef HAVE_GMTIME_R
859 struct tm* ptime=gmtime(&now);
862 struct tm* ptime=gmtime_r(&now,&res);
865 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
867 // Remove expired entries.
869 ODBCConn conn(getHDBC());
870 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
871 string q = string("DELETE FROM replay WHERE expires < ") + timebuf;
872 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
873 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
874 log->error("error purging old replay cache entries");
875 log_error(hstmt, SQL_HANDLE_STMT);
877 SQLCloseCursor(hstmt);
879 // Look for a replay.
880 q = string("SELECT id FROM replay WHERE id='") + str + "'";
881 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
882 if (!SQL_SUCCEEDED(sr)) {
883 log->error("error searching replay cache");
884 log_error(hstmt, SQL_HANDLE_STMT);
885 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
886 throw SAMLException("Replay cache failed, please inform application support staff.");
889 // If we got a record, we return false.
890 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
891 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
894 SQLCloseCursor(hstmt);
896 #ifndef HAVE_GMTIME_R
897 ptime=gmtime(&expires);
899 ptime=gmtime_r(&expires,&res);
901 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
903 // Add it to the database.
904 q = string("INSERT replay VALUES('") + str + "'," + timebuf + ")";
905 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
906 if (!SQL_SUCCEEDED(sr)) {
907 log->error("error inserting replay cache entry", str);
908 log_error(hstmt, SQL_HANDLE_STMT);
909 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
910 throw SAMLException("Replay cache failed, please inform application support staff.");
913 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
920 SessionCache* new_odbc_ccache(const DOMElement* const & e)
922 return new ODBCCCache(e);
925 IPlugIn* new_odbc_replay(const DOMElement* e)
927 return new ODBCReplayCache(e);
931 extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
933 // register this ccache type
934 SAMLConfig::getConfig().getPlugMgr().regFactory(ODBC_REPLAYCACHE, &new_odbc_replay);
935 SPConfig::getConfig().SessionCacheManager.registerFactory(ODBC_SESSIONCACHE, &new_odbc_ccache);
939 extern "C" void SHIBODBC_EXPORTS saml_extension_term()
941 SAMLConfig::getConfig().getPlugMgr().unregFactory(ODBC_REPLAYCACHE);