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
35 # define SHIBODBC_EXPORTS __declspec(dllexport)
37 # define SHIBODBC_EXPORTS
40 #include <shib/shib-threads.h>
41 #include <shib-target/shib-target.h>
42 #include <log4cpp/Category.hh>
49 #ifdef HAVE_LIBDMALLOCXX
55 using namespace shibboleth;
56 using namespace shibtarget;
57 using namespace log4cpp;
59 #define PLUGIN_VER_MAJOR 3
60 #define PLUGIN_VER_MINOR 0
62 #define COLSIZE_KEY 64
63 #define COLSIZE_APPLICATION_ID 256
64 #define COLSIZE_ADDRESS 128
65 #define COLSIZE_PROVIDER_ID 256
66 #define LONGDATA_BUFLEN 2048
70 cookie VARCHAR(64) PRIMARY KEY,
71 application_id VARCHAR(256),
77 provider VARCHAR(256),
84 #define REPLAY_TABLE \
85 "CREATE TABLE replay (id VARCHAR(255) PRIMARY KEY, " \
86 "expires TIMESTAMP, " \
89 static const XMLCh ConnectionString[] =
90 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
91 chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
93 static const XMLCh cleanupInterval[] =
94 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
95 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
97 static const XMLCh cacheTimeout[] =
98 { 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 };
99 static const XMLCh odbcTimeout[] =
100 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
101 static const XMLCh storeAttributes[] =
102 { 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 };
105 ODBCConn(SQLHDBC conn) : handle(conn) {}
106 ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
107 operator SQLHDBC() {return handle;}
111 class ODBCBase : public virtual saml::IPlugIn
114 ODBCBase(const DOMElement* e);
119 log4cpp::Category* log;
122 //ThreadKey* m_mysql;
123 const DOMElement* m_root; // can only use this during initialization
126 static SQLHENV m_henv; // single handle for both plugins
127 bool m_bInitializedODBC; // tracks which class handled the process
129 pair<int,int> getVersion(SQLHDBC);
130 void log_error(SQLHANDLE handle, SQLSMALLINT htype);
133 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
136 extern "C" void shib_mysql_destroy_handle(void* data)
138 MYSQL* mysql = (MYSQL*) data;
139 if (mysql) mysql_close(mysql);
143 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
146 saml::NDC ndc("ODBCBase");
148 log = &(Category::getInstance("shibtarget.SessionCache.ODBC"));
149 //m_mysql = ThreadKey::create(&shib_mysql_destroy_handle);
151 if (m_henv != SQL_NULL_HANDLE) {
152 log->info("ODBC already initialized");
156 // Enable connection pooling.
157 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
159 // Allocate the environment.
160 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
161 throw ConfigurationException("ODBC failed to initialize.");
164 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
166 log->info("ODBC initialized");
167 m_bInitializedODBC = true;
169 // Grab connection string from the configuration.
170 e=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,ConnectionString);
171 if (!e || !e->hasChildNodes()) {
173 throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
175 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
176 m_connstring=arg.get();
178 // Connect and check version.
179 SQLHDBC conn=getHDBC();
180 pair<int,int> v=getVersion(conn);
181 SQLFreeHandle(SQL_HANDLE_DBC,conn);
183 // Make sure we've got the right version.
184 if (v.first != PLUGIN_VER_MAJOR) {
186 log->crit("unknown database version: %d.%d", v.first, v.second);
187 throw SAMLException("Unknown cache database version.");
191 ODBCBase::~ODBCBase()
194 if (m_bInitializedODBC)
195 SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
196 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 SQLCloseCursor(hstmt);
260 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
261 return pair<int,int>(major,minor);
264 SQLCloseCursor(hstmt);
265 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
266 log->error("no rows returned in version query");
267 throw SAMLException("ODBCBase::getVersion failed to read version from database");
270 class ODBCCCache : public ODBCBase, virtual public ISessionCache, virtual public ISessionCacheStore
273 ODBCCCache(const DOMElement* e);
274 virtual ~ODBCCCache();
276 // Delegate all the ISessionCache methods.
278 const IApplication* application,
279 const IEntityDescriptor* source,
280 const char* client_addr,
281 const SAMLSubject* subject,
282 const char* authnContext,
283 const SAMLResponse* tokens
285 { return m_cache->insert(application,source,client_addr,subject,authnContext,tokens); }
286 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr)
287 { return m_cache->find(key,application,client_addr); }
288 void remove(const char* key, const IApplication* application, const char* client_addr)
289 { m_cache->remove(key,application,client_addr); }
291 bool setBackingStore(ISessionCacheStore*) {return false;}
293 // Store methods handle the database work
296 const IApplication* application,
297 const ISessionCacheEntry* entry,
304 string& applicationId,
305 string& clientAddress,
308 string& authnContext,
315 HRESULT onRead(const char* key, time_t& accessed);
316 HRESULT onRead(const char* key, string& tokens);
317 HRESULT onUpdate(const char* key, const char* tokens=NULL, time_t accessed=0);
318 HRESULT onDelete(const char* key);
323 bool m_storeAttributes;
324 ISessionCache* m_cache;
325 CondWait* shutdown_wait;
327 Thread* cleanup_thread;
329 static void* cleanup_fcn(void*); // XXX Assumed an ODBCCCache
332 ODBCCCache::ODBCCCache(const DOMElement* e) : ODBCBase(e), m_storeAttributes(false)
335 saml::NDC ndc("ODBCCCache");
338 m_cache = dynamic_cast<ISessionCache*>(
339 SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::MemorySessionCacheType, m_root)
341 if (!m_cache->setBackingStore(this)) {
343 throw SAMLException("Unable to register ODBC cache plugin as a cache store.");
346 shutdown_wait = CondWait::create();
349 // Load our configuration details...
350 const XMLCh* tag=m_root->getAttributeNS(NULL,storeAttributes);
351 if (tag && *tag && (*tag==chLatin_t || *tag==chDigit_1))
352 m_storeAttributes=true;
354 // Initialize the cleanup thread
355 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
358 ODBCCCache::~ODBCCCache()
361 shutdown_wait->signal();
362 cleanup_thread->join(NULL);
366 HRESULT ODBCCCache::onCreate(
368 const IApplication* application,
369 const ISessionCacheEntry* entry,
376 saml::NDC ndc("onCreate");
379 // Get XML data from entry. Default is not to return SAML objects.
380 const char* context=entry->getAuthnContext();
381 pair<const char*,const SAMLSubject*> subject=entry->getSubject();
382 pair<const char*,const SAMLResponse*> tokens=entry->getTokens();
384 // Stringify timestamp.
387 #ifndef HAVE_GMTIME_R
388 struct tm* ptime=gmtime(&created);
391 struct tm* ptime=gmtime_r(&created,&res);
394 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
396 // Prepare insert statement.
398 q << "INSERT state VALUES ('" << key << "','" << application->getId() << "'," << timebuf << "," << timebuf
399 << ",'" << entry->getClientAddress() << "'," << majorVersion << "," << minorVersion << ",'" << entry->getProviderId()
402 if (log->isDebugEnabled())
403 log->debug("SQL insert: %s", q.str().c_str());
405 // Get statement handle.
407 ODBCConn conn(getHDBC());
408 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
410 // Bind text parameters to statement.
411 SQLINTEGER cbSubject=SQL_LEN_DATA_AT_EXEC(0),cbContext=SQL_LEN_DATA_AT_EXEC(0),cbTokens;
412 if (!m_storeAttributes || !tokens.first)
413 cbTokens=SQL_NULL_DATA;
415 cbTokens=SQL_LEN_DATA_AT_EXEC(0);
416 SQLRETURN sr=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)subject.first,0,&cbSubject);
417 if (!SQL_SUCCEEDED(sr))
418 log_error(hstmt, SQL_HANDLE_STMT);
419 sr=SQLBindParameter(hstmt,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)context,0,&cbContext);
420 if (!SQL_SUCCEEDED(sr))
421 log_error(hstmt, SQL_HANDLE_STMT);
422 sr=SQLBindParameter(hstmt,3,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)tokens.first,0,&cbTokens);
423 if (!SQL_SUCCEEDED(sr))
424 log_error(hstmt, SQL_HANDLE_STMT);
426 // Execute statement.
427 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
428 if (sr==SQL_NEED_DATA) {
429 // Loop to send text data into driver.
430 // pData is set each round by the driver to the pointers we bound above.
432 sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
433 while (sr==SQL_NEED_DATA) {
434 size_t len=strlen(pData);
436 size_t amt = min(LONGDATA_BUFLEN,len);
437 SQLPutData(hstmt, pData, amt);
441 sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
446 if (!SQL_SUCCEEDED(sr)) {
447 log->error("failed to insert record into database");
448 log_error(hstmt, SQL_HANDLE_STMT);
452 SQLCloseCursor(hstmt);
453 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
457 HRESULT ODBCCCache::onRead(
459 string& applicationId,
460 string& clientAddress,
463 string& authnContext,
472 saml::NDC ndc("onRead");
475 log->debug("searching database...");
478 ODBCConn conn(getHDBC());
479 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
481 string q = string("SELECT application_id,ctime,atime,addr,major,minor,provider,subject,authn_context,tokens FROM state WHERE cookie='") + key + "'";
482 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
483 if (!SQL_SUCCEEDED(sr)) {
484 log->error("error searching for (%s)",key);
485 log_error(hstmt, SQL_HANDLE_STMT);
486 SQLCloseCursor(hstmt);
487 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
491 SQLINTEGER major,minor;
492 SQL_TIMESTAMP_STRUCT atime,ctime;
493 SQLCHAR application_id[COLSIZE_APPLICATION_ID+1];
494 SQLCHAR addr[COLSIZE_ADDRESS+1];
495 SQLCHAR provider_id[COLSIZE_PROVIDER_ID+1];
497 // Bind simple output columns.
498 SQLBindCol(hstmt,1,SQL_C_CHAR,application_id,sizeof(application_id),NULL);
499 SQLBindCol(hstmt,2,SQL_C_TYPE_TIMESTAMP,&ctime,0,NULL);
500 SQLBindCol(hstmt,3,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
501 SQLBindCol(hstmt,4,SQL_C_CHAR,addr,sizeof(addr),NULL);
502 SQLBindCol(hstmt,5,SQL_C_SLONG,&major,0,NULL);
503 SQLBindCol(hstmt,6,SQL_C_SLONG,&minor,0,NULL);
504 SQLBindCol(hstmt,7,SQL_C_CHAR,provider_id,sizeof(provider_id),NULL);
506 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
507 SQLCloseCursor(hstmt);
508 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
512 log->debug("session found, tranfering data back into memory");
514 // Copy back simple data.
515 applicationId = (char*)application_id;
516 clientAddress = (char*)addr;
517 majorVersion = major;
518 minorVersion = minor;
519 providerId = (char*)provider_id;
522 t.tm_sec=ctime.second;
523 t.tm_min=ctime.minute;
524 t.tm_hour=ctime.hour;
526 t.tm_mon=ctime.month-1;
527 t.tm_year=ctime.year-1900;
529 #if defined(HAVE_TIMEGM)
532 // Windows, and hopefully most others...?
533 created = mktime(&t) - timezone;
535 t.tm_sec=atime.second;
536 t.tm_min=atime.minute;
537 t.tm_hour=atime.hour;
539 t.tm_mon=atime.month-1;
540 t.tm_year=atime.year-1900;
542 #if defined(HAVE_TIMEGM)
545 // Windows, and hopefully most others...?
546 accessed = mktime(&t) - timezone;
549 // Extract text data.
550 string* ptrs[] = {&subject, &authnContext, &tokens};
553 SQLCHAR buf[LONGDATA_BUFLEN];
554 for (int i=0; i<3; i++) {
555 while ((sr=SQLGetData(hstmt,i+8,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
556 if (!SUCCEEDED(sr)) {
557 log->error("error while reading text field from result set");
558 log_error(hstmt, SQL_HANDLE_STMT);
562 ptrs[i]->append((char*)buf);
566 SQLCloseCursor(hstmt);
567 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
571 HRESULT ODBCCCache::onRead(const char* key, time_t& accessed)
574 saml::NDC ndc("onRead");
577 log->debug("reading last access time from database");
580 ODBCConn conn(getHDBC());
581 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
583 string q = string("SELECT atime FROM state WHERE cookie='") + key + "'";
584 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
585 if (!SQL_SUCCEEDED(sr)) {
586 log->error("error searching for (%s)",key);
587 log_error(hstmt, SQL_HANDLE_STMT);
588 SQLCloseCursor(hstmt);
589 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
593 SQL_TIMESTAMP_STRUCT atime;
594 SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&atime,0,NULL);
596 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
597 log->warn("session expected, but not found in database");
598 SQLCloseCursor(hstmt);
599 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
603 SQLCloseCursor(hstmt);
604 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
607 t.tm_sec=atime.second;
608 t.tm_min=atime.minute;
609 t.tm_hour=atime.hour;
611 t.tm_mon=atime.month-1;
612 t.tm_year=atime.year-1900;
614 #if defined(HAVE_TIMEGM)
617 // Windows, and hopefully most others...?
618 accessed = mktime(&t) - timezone;
623 HRESULT ODBCCCache::onRead(const char* key, string& tokens)
626 saml::NDC ndc("onRead");
629 if (!m_storeAttributes)
632 log->debug("reading cached tokens from database");
635 ODBCConn conn(getHDBC());
636 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
638 string q = string("SELECT tokens FROM state WHERE cookie='") + key + "'";
639 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
640 if (!SQL_SUCCEEDED(sr)) {
641 log->error("error searching for (%s)",key);
642 log_error(hstmt, SQL_HANDLE_STMT);
643 SQLCloseCursor(hstmt);
644 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
648 if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
649 log->warn("session expected, but not found in database");
650 SQLCloseCursor(hstmt);
651 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
657 SQLCHAR buf[LONGDATA_BUFLEN];
658 while ((sr=SQLGetData(hstmt,1,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
659 if (!SUCCEEDED(sr)) {
660 log->error("error while reading text field from result set");
661 log_error(hstmt, SQL_HANDLE_STMT);
665 tokens += (char*)buf;
668 SQLCloseCursor(hstmt);
669 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
673 HRESULT ODBCCCache::onUpdate(const char* key, const char* tokens, time_t lastAccess)
676 saml::NDC ndc("onUpdate");
681 ODBCConn conn(getHDBC());
684 #ifndef HAVE_GMTIME_R
685 struct tm* ptime=gmtime(&lastAccess);
688 struct tm* ptime=gmtime_r(&lastAccess,&res);
691 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
694 q << "UPDATE state SET atime=" << timebuf << " WHERE cookie='" << key << "'";
696 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
697 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
700 if (!m_storeAttributes)
702 string q = string("UPDATE state SET tokens=? WHERE cookie='") + key + "'";
704 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
706 // Bind text parameters to statement.
707 SQLINTEGER cbTokens = tokens ? SQL_LEN_DATA_AT_EXEC(0) : SQL_NULL_DATA;
708 sr=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_LONGVARCHAR,LONGDATA_BUFLEN,0,(SQLPOINTER)tokens,0,&cbTokens);
710 // Execute statement.
711 sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
712 if (sr==SQL_NEED_DATA) {
713 // Loop to send text data into driver.
714 // pData is set each round by the driver to the pointers we bound above.
716 sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
717 while (sr==SQL_NEED_DATA) {
718 size_t len=strlen(pData);
720 size_t amt=min(LONGDATA_BUFLEN,len);
721 SQLPutData(hstmt, pData, amt);
725 sr=SQLParamData(hstmt,(SQLPOINTER*)&pData);
730 log->warn("onUpdate called with nothing to do!");
737 else if (!SQL_SUCCEEDED(sr)) {
738 log->error("error updating record (key=%s)", key);
739 log_error(hstmt, SQL_HANDLE_STMT);
745 SQLCloseCursor(hstmt);
746 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
750 HRESULT ODBCCCache::onDelete(const char* key)
753 saml::NDC ndc("onDelete");
757 ODBCConn conn(getHDBC());
758 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
759 string q = string("DELETE FROM state WHERE cookie='") + key + "'";
760 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
765 else if (!SQL_SUCCEEDED(sr)) {
766 log->error("error deleting record (key=%s)", key);
767 log_error(hstmt, SQL_HANDLE_STMT);
773 SQLCloseCursor(hstmt);
774 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
778 void ODBCCCache::cleanup()
781 saml::NDC ndc("cleanup");
784 Mutex* mutex = Mutex::create();
787 int timeout_life = 0;
789 // Load our configuration details...
790 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
792 rerun_timer = XMLString::parseInt(tag);
794 // search for 'mysql-cache-timeout' and then the regular cache timeout
795 tag=m_root->getAttributeNS(NULL,odbcTimeout);
797 timeout_life = XMLString::parseInt(tag);
799 tag=m_root->getAttributeNS(NULL,cacheTimeout);
801 timeout_life = XMLString::parseInt(tag);
804 if (rerun_timer <= 0)
805 rerun_timer = 300; // rerun every 5 minutes
807 if (timeout_life <= 0)
808 timeout_life = 28800; // timeout after 8 hours
812 log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
814 while (shutdown == false) {
815 shutdown_wait->timedwait(mutex, rerun_timer);
817 if (shutdown == true)
820 // Find all the entries in the database that haven't been used
821 // recently In particular, find all entries that have not been
822 // accessed in 'timeout_life' seconds.
824 time_t stale=time(NULL)-timeout_life;
825 #ifndef HAVE_GMTIME_R
826 struct tm* ptime=gmtime(&stale);
829 struct tm* ptime=gmtime_r(&stale,&res);
832 strftime(timebuf,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
834 string q = string("DELETE state WHERE atime < ") + timebuf;
837 ODBCConn conn(getHDBC());
838 SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
839 SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
840 if (sr!=SQL_NO_DATA && !SQL_SUCCEEDED(sr)) {
841 log->error("error purging old records");
842 log_error(hstmt, SQL_HANDLE_STMT);
845 SQLINTEGER rowcount=0;
846 sr=SQLRowCount(hstmt,&rowcount);
847 if (SQL_SUCCEEDED(sr) && rowcount > 0)
848 log->info("purging %d old sessions",rowcount);
850 SQLCloseCursor(hstmt);
851 SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
854 log->info("cleanup thread exiting...");
861 void* ODBCCCache::cleanup_fcn(void* cache_p)
863 ODBCCCache* cache = (ODBCCCache*)cache_p;
865 // First, let's block all signals
866 Thread::mask_all_signals();
868 // Now run the cleanup process.
874 class MySQLReplayCache : public MySQLBase, virtual public IReplayCache
877 MySQLReplayCache(const DOMElement* e);
878 virtual ~MySQLReplayCache() {}
880 bool check(const XMLCh* str, time_t expires) {auto_ptr_XMLCh temp(str); return check(temp.get(),expires);}
881 bool check(const char* str, time_t expires);
884 MySQLReplayCache::MySQLReplayCache(const DOMElement* e) : MySQLBase(e)
887 saml::NDC ndc("MySQLReplayCache");
890 log = &(Category::getInstance("shibmysql.ReplayCache"));
893 bool MySQLReplayCache::check(const char* str, time_t expires)
896 saml::NDC ndc("check");
899 // Remove expired entries
900 string q = string("DELETE FROM replay WHERE expires < NOW()");
901 MYSQL* mysql = getMYSQL();
902 if (mysql_query(mysql, q.c_str())) {
903 const char* err=mysql_error(mysql);
904 log->error("Error deleting expired entries: %s", err);
905 if (isCorrupt(err) && repairTable(mysql,"replay")) {
907 if (mysql_query(mysql, q.c_str()))
908 log->error("Error deleting expired entries: %s", mysql_error(mysql));
912 string q2 = string("SELECT id FROM replay WHERE id='") + str + "'";
913 if (mysql_query(mysql, q2.c_str())) {
914 const char* err=mysql_error(mysql);
915 log->error("Error searching for %s: %s", str, err);
916 if (isCorrupt(err) && repairTable(mysql,"replay")) {
917 if (mysql_query(mysql, q2.c_str())) {
918 log->error("Error retrying search for %s: %s", str, mysql_error(mysql));
919 throw SAMLException("Replay cache failed, please inform application support staff.");
923 throw SAMLException("Replay cache failed, please inform application support staff.");
927 MYSQL_RES* rows = mysql_store_result(mysql);
928 if (rows && mysql_num_rows(rows)>0) {
929 mysql_free_result(rows);
934 q3 << "INSERT INTO replay VALUES('" << str << "'," << "FROM_UNIXTIME(" << expires << "))";
936 // then add it to the database
937 if (mysql_query(mysql, q3.str().c_str())) {
938 const char* err=mysql_error(mysql);
939 log->error("Error inserting %s: %s", str, err);
940 if (isCorrupt(err) && repairTable(mysql,"state")) {
942 if (mysql_query(mysql, q3.str().c_str())) {
943 log->error("Error inserting %s: %s", str, mysql_error(mysql));
944 throw SAMLException("Replay cache failed, please inform application support staff.");
948 throw SAMLException("Replay cache failed, please inform application support staff.");
954 IPlugIn* new_mysql_replay(const DOMElement* e)
956 return new MySQLReplayCache(e);
960 /*************************************************************************
961 * The registration functions here...
964 IPlugIn* new_odbc_ccache(const DOMElement* e)
966 return new ODBCCCache(e);
970 extern "C" int SHIBODBC_EXPORTS saml_extension_init(void*)
972 // register this ccache type
973 // SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCReplayCacheType, &new_odbc_replay);
974 SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::ODBCSessionCacheType, &new_odbc_ccache);
978 extern "C" void SHIBODBC_EXPORTS saml_extension_term()
980 SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCSessionCacheType);
981 // SAMLConfig::getConfig().getPlugMgr().unregFactory(shibtarget::XML::ODBCReplayCacheType);