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/shib-threads.h>
42 #include <shib-target/shib-target.h>
43 #include <log4cpp/Category.hh>
51 #ifdef HAVE_LIBDMALLOCXX
57 using namespace shibboleth;
58 using namespace shibtarget;
59 using namespace log4cpp;
61 #define PLUGIN_VER_MAJOR 3
62 #define PLUGIN_VER_MINOR 0
64 #define COLSIZE_KEY 64
65 #define COLSIZE_APPLICATION_ID 256
66 #define COLSIZE_ADDRESS 128
67 #define COLSIZE_PROVIDER_ID 256
68 #define LONGDATA_BUFLEN 32768
72 cookie VARCHAR(64) PRIMARY KEY,
73 application_id VARCHAR(256),
79 provider VARCHAR(256),
86 #define REPLAY_TABLE \
87 "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
88 "expires TIMESTAMP, " \
91 static const XMLCh ConnectionString[] =
92 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
93 chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
95 static const XMLCh cleanupInterval[] =
96 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
97 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
99 static const XMLCh cacheTimeout[] =
100 { 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 };
101 static const XMLCh odbcTimeout[] =
102 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
103 static const XMLCh storeAttributes[] =
104 { 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 };
107 ODBCConn(SQLHDBC conn) : handle(conn) {}
108 ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
109 operator SQLHDBC() {return handle;}
113 class ODBCBase : public virtual saml::IPlugIn
116 ODBCBase(const DOMElement* e);
121 log4cpp::Category* log;
124 //ThreadKey* m_mysql;
125 const DOMElement* m_root; // can only use this during initialization
128 static SQLHENV m_henv; // single handle for both plugins
129 bool m_bInitializedODBC; // tracks which class handled the process
130 static const char* p_connstring;
132 pair<int,int> getVersion(SQLHDBC);
133 void log_error(SQLHANDLE handle, SQLSMALLINT htype);
136 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
137 const char* ODBCBase::p_connstring = NULL;
139 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
142 saml::NDC ndc("ODBCBase");
144 log = &(Category::getInstance("shibtarget.ODBC"));
146 if (m_henv == SQL_NULL_HANDLE) {
147 // Enable connection pooling.
148 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
150 // Allocate the environment.
151 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
152 throw ConfigurationException("ODBC failed to initialize.");
155 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
157 log->info("ODBC initialized");
158 m_bInitializedODBC = true;
161 // Grab connection string from the configuration.
162 e=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,ConnectionString);
163 if (!e || !e->hasChildNodes()) {
166 throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
168 m_connstring=p_connstring;
171 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
172 m_connstring=arg.get();
173 p_connstring=m_connstring.c_str();
176 // Connect and check version.
177 SQLHDBC conn=getHDBC();
178 pair<int,int> v=getVersion(conn);
179 SQLFreeHandle(SQL_HANDLE_DBC,conn);
181 // Make sure we've got the right version.
182 if (v.first != PLUGIN_VER_MAJOR) {
184 log->crit("unknown database version: %d.%d", v.first, v.second);
185 throw SAMLException("Unknown cache database version.");
189 ODBCBase::~ODBCBase()
192 if (m_bInitializedODBC)
193 SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
194 m_bInitializedODBC=false;
195 m_henv = SQL_NULL_HANDLE;
199 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
209 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
210 if (SQL_SUCCEEDED(ret))
211 log->error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
212 } while(SQL_SUCCEEDED(ret));
215 SQLHDBC ODBCBase::getHDBC()
218 saml::NDC ndc("getMYSQL");
223 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
224 if (!SQL_SUCCEEDED(sr)) {
225 log->error("failed to allocate connection handle");
226 log_error(m_henv, SQL_HANDLE_ENV);
227 throw SAMLException("ODBCBase::getHDBC failed to allocate connection handle");
230 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
231 if (!SQL_SUCCEEDED(sr)) {
232 log->error("failed to connect to database");
233 log_error(handle, SQL_HANDLE_DBC);
234 throw SAMLException("ODBCBase::getHDBC failed to connect to database");
240 pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
242 // Grab the version number from the database.
244 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
246 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
247 if (!SQL_SUCCEEDED(sr)) {
248 log->error("failed to read version from database");
249 log_error(hstmt, SQL_HANDLE_STMT);
250 throw SAMLException("ODBCBase::getVersion failed to read version from database");
255 SQLBindCol(hstmt,1,SQL_C_SLONG,&major,0,NULL);
256 SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
258 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
259 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
260 return pair<int,int>(major,minor);
263 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
264 log->error("no rows returned in version query");
265 throw SAMLException("ODBCBase::getVersion failed to read version from database");
268 class ODBCCCache : public ODBCBase, virtual public ISessionCache, virtual public ISessionCacheStore
271 ODBCCCache(const DOMElement* e);
272 virtual ~ODBCCCache();
274 // Delegate all the ISessionCache methods.
276 const IApplication* application,
277 const IEntityDescriptor* source,
278 const char* client_addr,
279 const SAMLSubject* subject,
280 const char* authnContext,
281 const SAMLResponse* tokens
283 { return m_cache->insert(application,source,client_addr,subject,authnContext,tokens); }
284 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
285 { return m_cache->find(key,application,client_addr); }
286 void remove(const char* key, const IApplication* application, const char* client_addr)
287 { m_cache->remove(key,application,client_addr); }
289 bool setBackingStore(ISessionCacheStore*) {return false;}
291 // Store methods handle the database work
294 const IApplication* application,
295 const ISessionCacheEntry* entry,
302 string& applicationId,
303 string& clientAddress,
306 string& authnContext,
313 HRESULT onRead(const char* key, time_t& accessed);
314 HRESULT onRead(const char* key, string& tokens);
315 HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
316 HRESULT onDelete(const char* key);
321 bool m_storeAttributes;
322 ISessionCache* m_cache;
323 CondWait* shutdown_wait;
325 Thread* cleanup_thread;
327 static void* cleanup_fcn(void*); // XXX Assumed an ODBCCCache
330 ODBCCCache::ODBCCCache(const DOMElement* e) : ODBCBase(e), m_storeAttributes(false)
333 saml::NDC ndc("ODBCCCache");
335 log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
337 m_cache = dynamic_cast<ISessionCache*>(
338 SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::MemorySessionCacheType, m_root)
340 if (!m_cache->setBackingStore(this)) {
342 throw SAMLException("Unable to register ODBC cache plugin as a cache store.");
345 shutdown_wait = CondWait::create();
348 // Load our configuration details...
349 const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
350 if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
351 m_storeAttributes=true;
353 // Initialize the cleanup thread
354 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
357 ODBCCCache::~ODBCCCache()
360 shutdown_wait->signal();
361 cleanup_thread->join(NULL);
365 void appendXML(ostream& os, const char* str)
367 const char* pos=strchr(str,'\'');
369 os.write(str,pos-str);
372 pos=strchr(str,'\'');
377 HRESULT ODBCCCache::onCreate(
379 const IApplication* application,
380 const ISessionCacheEntry* entry,
387 saml::NDC ndc("onCreate");
390 // Get XML data from entry. Default is not to return SAML objects.
391 const char* context=entry->getAuthnContext();
392 pair<const char*,const SAMLSubject*> subject=entry->getSubject();
393 pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
395 // Stringify timestamp.
398 #ifndef HAVE_GMTIME_R
399 struct tm* ptime=gmtime(&created);
402 struct tm* ptime=gmtime_r(&created,&res);
405 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
407 // Prepare insert statement.
409 q << "INSERT state VALUES ('" << key << "','" << application->getId() << "'," << timebuf << "," << timebuf
410 << ",'" << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId()
412 appendXML(q,subject.first);
414 appendXML(q,context);
416 if (m_storeAttributes && tokens.first) {
418 appendXML(q,tokens.first);
423 if (log->isDebugEnabled())
424 log->debug("SQL insert: %s", q.str().c_str());
426 // Get statement handle.
428 ODBCConn conn(getHDBC());
429 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
431 // Execute statement.
433 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
434 if (!SQL_SUCCEEDED(sr)) {
435 log->error("failed to insert record into database");
436 log_error(hstmt, SQL_HANDLE_STMT);
440 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
444 HRESULT ODBCCCache::onRead(
446 string& applicationId,
447 string& clientAddress,
450 string& authnContext,
459 saml::NDC ndc("onRead");
462 log->debug("searching database...");
465 ODBCConn conn(getHDBC());
466 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
468 string q = string("SELECT application_id,ctime,atime,addr,major,minor,provider,subject,authn_context,tokens FROM state WHERE cookie='") + key + "'";
469 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
470 if (!SQL_SUCCEEDED(sr)) {
471 log->error("error searching for (%s)",key);
472 log_error(hstmt, SQL_HANDLE_STMT);
473 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
477 SQLINTEGER major,minor;
478 SQL_TIMESTAMP_STRUCT atime,ctime;
479 SQLCHAR application_id[COLSIZE_APPLICATION_ID+1];
480 SQLCHAR addr[COLSIZE_ADDRESS+1];
481 SQLCHAR provider_id[COLSIZE_PROVIDER_ID+1];
483 // Bind simple output columns.
484 SQLBindCol(hstmt,1,SQL_C_CHAR,application_id,sizeof(application_id),NULL);
485 SQLBindCol(hstmt,2,SQL_C_TYPE_TIMESTAMP,&ctime,0,NULL);
486 SQLBindCol(hstmt,3,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
487 SQLBindCol(hstmt,4,SQL_C_CHAR,addr,sizeof(addr),NULL);
488 SQLBindCol(hstmt,5,SQL_C_SLONG,&major,0,NULL);
489 SQLBindCol(hstmt,6,SQL_C_SLONG,&minor,0,NULL);
490 SQLBindCol(hstmt,7,SQL_C_CHAR,provider_id,sizeof(provider_id),NULL);
492 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
493 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
497 log->debug("session found, tranfering data back into memory");
499 // Copy back simple data.
500 applicationId = (char*)application_id;
501 clientAddress = (char*)addr;
502 majorVersion = major;
503 minorVersion = minor;
504 providerId = (char*)provider_id;
507 t.tm_sec=ctime.second;
508 t.tm_min=ctime.minute;
509 t.tm_hour=ctime.hour;
511 t.tm_mon=ctime.month-1;
512 t.tm_year=ctime.year-1900;
514 #if defined(HAVE_TIMEGM)
517 // Windows, and hopefully most others...?
518 created = mktime(&t) - timezone;
520 t.tm_sec=atime.second;
521 t.tm_min=atime.minute;
522 t.tm_hour=atime.hour;
524 t.tm_mon=atime.month-1;
525 t.tm_year=atime.year-1900;
527 #if defined(HAVE_TIMEGM)
530 // Windows, and hopefully most others...?
531 accessed = mktime(&t) - timezone;
534 // Extract text data.
535 string* ptrs[] = {&subject, &authnContext, &tokens};
538 SQLCHAR buf[LONGDATA_BUFLEN];
539 for (int i=0; i<3; i++) {
540 while ((sr=SQLGetData(hstmt,i+8,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
541 if (!SUCCEEDED(sr)) {
542 log->error("error while reading text field from result set");
543 log_error(hstmt, SQL_HANDLE_STMT);
547 ptrs[i]->append((char*)buf);
551 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
555 HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
558 saml::NDC ndc("onRead");
561 log->debug("reading last access time from database");
564 ODBCConn conn(getHDBC());
565 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
567 string q = string("SELECT atime FROM state WHERE cookie='") + key + "'";
568 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
569 if (!SQL_SUCCEEDED(sr)) {
570 log->error("error searching for (%s)",key);
571 log_error(hstmt, SQL_HANDLE_STMT);
572 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
576 SQL_TIMESTAMP_STRUCT atime;
577 SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
579 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
580 log->warn("session expected, but not found in database");
581 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
585 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
588 t.tm_sec=atime.second;
589 t.tm_min=atime.minute;
590 t.tm_hour=atime.hour;
592 t.tm_mon=atime.month-1;
593 t.tm_year=atime.year-1900;
595 #if defined(HAVE_TIMEGM)
598 // Windows, and hopefully most others...?
599 accessed = mktime(&t) - timezone;
604 HRESULT ODBCCCache::onRead(const char* key, string& tokens)
607 saml::NDC ndc("onRead");
610 if (!m_storeAttributes)
613 log->debug("reading cached tokens from database");
616 ODBCConn conn(getHDBC());
617 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
619 string q = string("SELECT tokens FROM state WHERE cookie='") + key + "'";
620 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
621 if (!SQL_SUCCEEDED(sr)) {
622 log->error("error searching for (%s)",key);
623 log_error(hstmt, SQL_HANDLE_STMT);
624 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
628 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
629 log->warn("session expected, but not found in database");
630 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
636 SQLCHAR buf[LONGDATA_BUFLEN];
637 while ((sr=SQLGetData(hstmt,1,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
638 if (!SUCCEEDED(sr)) {
639 log->error("error while reading text field from result set");
640 log_error(hstmt, SQL_HANDLE_STMT);
644 tokens += (char*)buf;
647 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
651 HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
654 saml::NDC ndc("onUpdate");
660 #ifndef HAVE_GMTIME_R
661 struct tm* ptime=gmtime(&lastAccess);
664 struct tm* ptime=gmtime_r(&lastAccess,&res);
667 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
669 q << "UPDATE state SET atime=" << timebuf << " WHERE cookie='" << key << "'";
672 if (!m_storeAttributes)
674 q << "UPDATE state SET tokens=";
682 q << "WHERE cookie='" << key << "'";
685 log->warn("onUpdate called with nothing to do!");
691 ODBCConn conn(getHDBC());
692 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
693 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
696 else if (!SQL_SUCCEEDED(sr)) {
697 log->error("error updating record (key=%s)", key);
698 log_error(hstmt, SQL_HANDLE_STMT);
702 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
706 HRESULT ODBCCCache::onDelete(const char* key)
709 saml::NDC ndc("onDelete");
713 ODBCConn conn(getHDBC());
714 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
715 string q = string("DELETE FROM state WHERE cookie='") + key + "'";
716 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
721 else if (!SQL_SUCCEEDED(sr)) {
722 log->error("error deleting record (key=%s)", key);
723 log_error(hstmt, SQL_HANDLE_STMT);
727 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
731 void ODBCCCache::cleanup()
734 saml::NDC ndc("cleanup");
737 Mutex* mutex = Mutex::create();
740 int timeout_life = 0;
742 // Load our configuration details...
743 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
745 rerun_timer = XMLString::parseInt(tag);
747 // search for 'mysql-cache-timeout' and then the regular cache timeout
748 tag=m_root->getAttributeNS(NULL,odbcTimeout);
750 timeout_life = XMLString::parseInt(tag);
752 tag=m_root->getAttributeNS(NULL,cacheTimeout);
754 timeout_life = XMLString::parseInt(tag);
757 if (rerun_timer <= 0)
758 rerun_timer = 300; // rerun every 5 minutes
760 if (timeout_life <= 0)
761 timeout_life = 28800; // timeout after 8 hours
765 log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
767 while (shutdown == false) {
768 shutdown_wait->timedwait(mutex, rerun_timer);
770 if (shutdown == true)
773 // Find all the entries in the database that haven't been used
774 // recently In particular, find all entries that have not been
775 // accessed in 'timeout_life' seconds.
777 time_t stale=time(NULL)-timeout_life;
778 #ifndef HAVE_GMTIME_R
779 struct tm* ptime=gmtime(&stale);
782 struct tm* ptime=gmtime_r(&stale,&res);
785 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
787 string q = string("DELETE state WHERE atime < ") + timebuf;
790 ODBCConn conn(getHDBC());
791 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
792 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
793 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
794 log->error("error purging old records");
795 log_error(hstmt, SQL_HANDLE_STMT);
798 SQLINTEGER rowcount=0;
799 sr=SQLRowCount(hstmt,&rowcount);
800 if (SQL_SUCCEEDED(sr) && rowcount > 0)
801 log->info("purging %d old sessions",rowcount);
803 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
806 log->info("cleanup thread exiting...");
813 void* ODBCCCache::cleanup_fcn(void* cache_p)
815 ODBCCCache* cache = (ODBCCCache*)cache_p;
817 // First, let's block all signals
818 Thread::mask_all_signals();
820 // Now run the cleanup process.
826 class ODBCReplayCache : public ODBCBase, virtual public IReplayCache
829 ODBCReplayCache(const DOMElement* e);
830 virtual ~ODBCReplayCache() {}
832 bool check(const XMLCh* str, time_t expires) {auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
833 bool check(const char* str, time_t expires);
836 ODBCReplayCache::ODBCReplayCache(const DOMElement* e) : ODBCBase(e)
839 saml::NDC ndc("ODBCReplayCache");
841 log = &(Category::getInstance("shibtarget.ReplayCache.ODBC"));
844 bool ODBCReplayCache::check(const char* str, time_t expires)
847 saml::NDC ndc("check");
850 time_t now=time(NULL);
851 #ifndef HAVE_GMTIME_R
852 struct tm* ptime=gmtime(&now);
855 struct tm* ptime=gmtime_r(&now,&res);
858 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
860 // Remove expired entries.
862 ODBCConn conn(getHDBC());
863 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
864 string q = string("DELETE FROM replay WHERE expires < ") + timebuf;
865 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
866 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
867 log->error("error purging old replay cache entries");
868 log_error(hstmt, SQL_HANDLE_STMT);
870 SQLCloseCursor(hstmt);
872 // Look for a replay.
873 q = string("SELECT id FROM replay WHERE id='") + str + "'";
874 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
875 if (!SQL_SUCCEEDED(sr)) {
876 log->error("error searching replay cache");
877 log_error(hstmt, SQL_HANDLE_STMT);
878 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
879 throw SAMLException("Replay cache failed, please inform application support staff.");
882 // If we got a record, we return false.
883 if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
884 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
887 SQLCloseCursor(hstmt);
889 #ifndef HAVE_GMTIME_R
890 ptime=gmtime(&expires);
892 ptime=gmtime_r(&expires,&res);
894 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
896 // Add it to the database.
897 q = string("INSERT replay VALUES('") + str + "'," + timebuf + ")";
898 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
899 if (!SQL_SUCCEEDED(sr)) {
900 log->error("error inserting replay cache entry", str);
901 log_error(hstmt, SQL_HANDLE_STMT);
902 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
903 throw SAMLException("Replay cache failed, please inform application support staff.");
906 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
913 IPlugIn* new_odbc_ccache(const DOMElement* e)
915 return new ODBCCCache(e);
918 IPlugIn* new_odbc_replay(const DOMElement* e)
920 return new ODBCReplayCache(e);
924 extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
926 // register this ccache type
927 SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCReplayCacheType, &new_odbc_replay);
928 SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCSessionCacheType, &new_odbc_ccache);
932 extern "C" void SHIBODBC_EXPORTS saml_extension_term()
934 SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCSessionCacheType);
935 SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCReplayCacheType);