// // // Copyright (C) 2004 SIPfoundry Inc. // Licensed by SIPfoundry under the LGPL license. // // Copyright (C) 2004 Pingtel Corp. // Licensed to SIPfoundry under a Contributor Agreement. // // $$ ////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES // APPLICATION INCLUDES #include "os/OsLock.h" #include "os/OsDateTime.h" #include "os/OsFS.h" #include "os/OsSysLog.h" #include "net/Url.h" #include "xmlparser/tinyxml.h" #include "fastdb/fastdb.h" #include "sipdb/ResultSet.h" #include "sipdb/SIPDBManager.h" #include "sipdb/CredentialRow.h" #include "sipdb/CredentialDB.h" // DEFINES REGISTER( CredentialRow ); // MACROS // EXTERNAL FUNCTIONS // EXTERNAL VARIABLES // CONSTANTS // STRUCTS // TYPEDEFS // FORWARD DECLARATIONS // STATIC INITIALIZERS CredentialDB* CredentialDB::spInstance = NULL; OsMutex CredentialDB::sLockMutex (OsMutex::Q_FIFO); // GLOBAL VARIABLES UtlString CredentialDB::gUriKey("uri"); UtlString CredentialDB::gRealmKey("realm"); UtlString CredentialDB::gUseridKey("userid"); UtlString CredentialDB::gPasstokenKey("passtoken"); UtlString CredentialDB::gPintokenKey("pintoken"); UtlString CredentialDB::gAuthtypeKey("authtype"); /* ============================ CREATORS ================================== */ CredentialDB::CredentialDB ( const UtlString& name ) : mDatabaseName( name ) { // Access the shared table database SIPDBManager* pSIPDBManager = SIPDBManager::getInstance(); m_pFastDB = pSIPDBManager->getDatabase( name ); // If we are the first process to attach // then we need to load the DB int numusers = pSIPDBManager->getNumDatabaseProcesses (name); if ( numusers == 1 ) { // Load the file implicitly this->load(); } } CredentialDB::~CredentialDB() { OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## CredentialDB:: DESTRUCTOR"); } /* ============================ MANIPULATORS ============================== */ void CredentialDB::releaseInstance() { OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## CredentialDB:: releaseInstance() spInstance=%p", spInstance); // Critical Section here OsLock lock( sLockMutex ); // if it exists, delete the object and NULL out the pointer if (spInstance != NULL) { // unregister this table/process from the IMDB SIPDBManager::getInstance()->removeDatabase ( spInstance->mDatabaseName ); // NULL out the fastDB pointer also spInstance->m_pFastDB = NULL; delete spInstance; spInstance = NULL; } } OsStatus CredentialDB::load() { // Critical Section here OsLock lock( sLockMutex ); OsStatus result = OS_SUCCESS; if ( m_pFastDB != NULL ) { // Clean out the existing DB rows before loading // a new set from persistent storage removeAllRows (); UtlString fileName = SIPDBManager::getInstance()-> getConfigDirectory() + OsPath::separator + mDatabaseName + ".xml"; OsSysLog::add(FAC_DB, PRI_DEBUG, "CredentialDB::load loading \"%s\"", fileName.data()); TiXmlDocument doc ( fileName ); // Verify that we can load the file (i.e it must exist) if( doc.LoadFile() ) { // the checksum is used to determine if the db changed between reloads int loadChecksum = 0; TiXmlNode * rootNode = doc.FirstChild ("items"); if (rootNode != NULL) { // the folder node contains at least the name/displayname/ // and autodelete elements, it may contain others for( TiXmlNode *itemNode = rootNode->FirstChild( "item" ); itemNode; itemNode = itemNode->NextSibling( "item" ) ) { // Create a hash dictionary for element attributes UtlHashMap nvPairs; for( TiXmlNode *elementNode = itemNode->FirstChild(); elementNode; elementNode = elementNode->NextSibling() ) { // Bypass comments and other element types only interested // in parsing element attributes if ( elementNode->Type() == TiXmlNode::ELEMENT ) { UtlString elementName = elementNode->Value(); UtlString elementValue; result = SIPDBManager::getAttributeValue ( *itemNode, elementName, elementValue); // update the load checksum loadChecksum += ( elementName.hash() + elementValue.hash() ); if (result == OS_SUCCESS) { UtlString* collectableKey = new UtlString( elementName ); UtlString* collectableValue = new UtlString( elementValue ); nvPairs.insertKeyAndValue ( collectableKey, collectableValue ); } else if ( elementNode->FirstChild() == NULL ) { // Null Element value creaete a special // char string we have key and value so insert UtlString* collectableKey = new UtlString( elementName ); UtlString* collectableValue = new UtlString( SPECIAL_IMDB_NULL_VALUE ); nvPairs.insertKeyAndValue ( collectableKey, collectableValue ); } } } // If this is an old credentials file, it may not contain // the pintoken element - if not, duplicate the passtoken // to create it. if (!nvPairs.contains(&gPintokenKey)) { UtlString* pintokenKey = new UtlString(gPintokenKey); UtlString* pintokenValue = new UtlString(*((UtlString*)nvPairs.findValue(&gPasstokenKey))); nvPairs.insertKeyAndValue(pintokenKey, pintokenValue); } // Insert the item row into the IMDB insertRow ( nvPairs ); } } // Update the tableInfo table and determine if the db has // changed as a result of the reload (setting the // changed tableInfo field SIPDBManager::getInstance()-> updateDatabaseInfo( mDatabaseName, loadChecksum); } else { OsSysLog::add(FAC_DB, PRI_WARNING, "CredentialDB::load failed to load \"%s\"", fileName.data()); } } else { OsSysLog::add(FAC_DB, PRI_ERR, "CredentialDB::load failed - no DB"); result = OS_FAILED; } return result; } OsStatus CredentialDB::store() { // Critical Section here OsLock lock( sLockMutex ); OsStatus result = OS_SUCCESS; if ( m_pFastDB != NULL ) { UtlString fileName = SIPDBManager::getInstance()-> getConfigDirectory() + OsPath::separator + mDatabaseName + ".xml"; // Thread Local Storage m_pFastDB->attach(); // Search our memory for rows dbCursor< CredentialRow > cursor; // Select everything in the IMDB and add as item elements if present if ( cursor.select() > 0 ) { // Create an empty document TiXmlDocument document; // Create a hard coded standalone declaration section document.Parse (""); // Create the root node container TiXmlElement itemsElement ( "items" ); itemsElement.SetAttribute( "type", mDatabaseName.data() ); // metadata contains column names dbTableDescriptor* pTableMetaData = &CredentialRow::dbDescriptor; do { // Create an item container TiXmlElement itemElement ("item"); byte* base = (byte*)cursor.get(); // Add the column name value pairs for ( dbFieldDescriptor* fd = pTableMetaData->getFirstField(); fd != NULL; fd = fd->nextField ) { // if the column name does not contain the // np_prefix we must_presist it if ( strstr( fd->name, "np_" ) == NULL ) { // Create the a column element named after the IMDB column name TiXmlElement element (fd->name ); // See if the IMDB has the predefined SPECIAL_NULL_VALUE UtlString textValue; SIPDBManager::getFieldValue(base, fd, textValue); // If the value is not null append a text child element if ( textValue != SPECIAL_IMDB_NULL_VALUE ) { // Text type assumed here... @todo change this TiXmlText value ( textValue.data() ); // Store the column value in the element making this // coltextvalue element.InsertEndChild ( value ); } // Store this in the item tag as follows // // .. col1textvalue // .. col2textvalue // .... etc itemElement.InsertEndChild ( element ); } } // add the line to the element itemsElement.InsertEndChild ( itemElement ); } while ( cursor.next() ); // Attach the root node to the document document.InsertEndChild ( itemsElement ); document.SaveFile ( fileName ); } else { // database contains no rows so delete the file UtlString fileName = SIPDBManager::getInstance()-> getConfigDirectory() + OsPath::separator + mDatabaseName + ".xml"; if ( OsFileSystem::exists ( fileName ) ) { OsFileSystem::remove( fileName ); } } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); } else { result = OS_FAILED; } return result; } UtlBoolean CredentialDB::insertRow (const UtlHashMap& nvPairs) { // Note: identity inferred from the uri return insertRow ( Url (*((UtlString*)nvPairs.findValue(&gUriKey))), *((UtlString*)nvPairs.findValue(&gRealmKey)), *((UtlString*)nvPairs.findValue(&gUseridKey)), *((UtlString*)nvPairs.findValue(&gPasstokenKey)), *((UtlString*)nvPairs.findValue(&gPintokenKey)), *((UtlString*)nvPairs.findValue(&gAuthtypeKey))); } UtlBoolean CredentialDB::insertRow ( const Url& uri, const UtlString& realm, const UtlString& userid, const UtlString& passToken, const UtlString& pinToken, const UtlString& authType ) { UtlBoolean result = FALSE; UtlString identity; uri.getIdentity(identity); if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); // Search for a matching row before // deciding to update or insert dbCursor< CredentialRow > cursor(dbCursorForUpdate); // The URI field should be unique. dbQuery query; query="np_identity=",identity,"and realm=",realm; UtlString fullUri = uri.toString(); if ( cursor.select( query ) > 0 ) { do { // Update row (Identiy and realm already match so ignore cursor->uri = fullUri; cursor->userid = userid; cursor->passtoken = passToken; cursor->pintoken = pinToken; cursor->authtype = authType; cursor.update(); } while (cursor.next()); } else { // Insert new row CredentialRow row; row.np_identity = identity; row.realm = realm; row.uri = fullUri; row.userid = userid; row.passtoken = passToken; row.pintoken = pinToken; row.authtype = authType; insert (row); } // Either did an insert or an update result = TRUE; // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); // Table Data changed SIPDBManager::getInstance()-> setDatabaseChangedFlag(mDatabaseName, TRUE); } // Most likely an arg problem return result; } void CredentialDB::removeRows ( const Url& uri, const UtlString& realm ) { UtlString identity; uri.getIdentity(identity); if ( !identity.isNull() && (m_pFastDB != NULL)) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor(dbCursorForUpdate); dbQuery query; query="np_identity=",identity,"and realm=",realm; if (cursor.select(query) > 0) { cursor.removeAllSelected(); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); // Table Data changed SIPDBManager::getInstance()-> setDatabaseChangedFlag(mDatabaseName, TRUE); } } void CredentialDB::removeRows ( const Url& uri ) { UtlString identity; uri.getIdentity(identity); if ( !identity.isNull() && (m_pFastDB != NULL)) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor(dbCursorForUpdate); dbQuery query; query="np_identity=",identity; if (cursor.select(query) > 0) { cursor.removeAllSelected(); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); // Table Data changed SIPDBManager::getInstance()-> setDatabaseChangedFlag(mDatabaseName, TRUE); } } void CredentialDB::removeAllRows () { if ( m_pFastDB != NULL ) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor(dbCursorForUpdate); if (cursor.select() > 0) { cursor.removeAllSelected(); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); // Table Data changed SIPDBManager::getInstance()-> setDatabaseChangedFlag(mDatabaseName, TRUE); } } void CredentialDB::getAllRows( ResultSet& rResultSet ) const { // Clear the out any previous records rResultSet.destroyAll(); if (m_pFastDB != NULL) { // must do this first to ensure process/tls integrity m_pFastDB->attach(); dbCursor< CredentialRow > cursor; if ( cursor.select() > 0 ) { do { UtlHashMap record; UtlString* uriValue = new UtlString ( cursor->uri ); UtlString* realmValue = new UtlString ( cursor->realm ); UtlString* useridValue = new UtlString ( cursor->userid ); UtlString* passtokenValue = new UtlString ( cursor->passtoken ); UtlString* pintokenValue = new UtlString ( cursor->pintoken ); UtlString* authtypeValue = new UtlString ( cursor->authtype ); // Memory Leak fixes, make shallow copies of static keys UtlString* uriKey = new UtlString( gUriKey ); UtlString* realmKey = new UtlString( gRealmKey ); UtlString* useridKey = new UtlString( gUseridKey ); UtlString* passtokenKey = new UtlString( gPasstokenKey ); UtlString* pintokenKey = new UtlString( gPintokenKey ); UtlString* authtypeKey = new UtlString( gAuthtypeKey ); record.insertKeyAndValue ( uriKey, uriValue ); record.insertKeyAndValue ( realmKey, realmValue ); record.insertKeyAndValue ( useridKey, useridValue ); record.insertKeyAndValue ( passtokenKey, passtokenValue ); record.insertKeyAndValue ( pintokenKey, pintokenValue ); record.insertKeyAndValue ( authtypeKey, authtypeValue ); rResultSet.addValue(record); } while (cursor.next()); } // commit rows and also ensure process/tls integrity m_pFastDB->detach(0); } } UtlBoolean CredentialDB::getCredentialByUserid ( const Url& uri, const UtlString& realm, const UtlString& userid, UtlString& passtoken, UtlString& authType ) const { UtlBoolean found = FALSE; UtlString identity; uri.getIdentity(identity); // Match the row and return the passtoken and authtype if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor; dbQuery query; query="np_identity=",identity, \ "and realm=",realm, \ "and userid=",userid, \ "order by np_identity asc, realm asc"; if ( cursor.select( query ) > 0 ) { do { passtoken = cursor->passtoken; authType = cursor->authtype; found = TRUE; } while ( cursor.next() ); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); } return found; } UtlBoolean CredentialDB::getCredential ( const UtlString& userid, const UtlString& realm, Url& uri, UtlString& passtoken, UtlString& authType ) const { UtlBoolean found = FALSE; if ( !userid.isNull() && !realm.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); // Search to see if we have a Credential Row dbCursor< CredentialRow > cursor; dbQuery query; query="userid=",userid,"and realm=",realm; if ( cursor.select(query) > 0 ) { do { uri = cursor->uri; passtoken = cursor->passtoken; authType = cursor->authtype; } while ( cursor.next() ); found = TRUE; } // Commit the rows to memory - multiprocess workaround m_pFastDB->detach(0); } return found; } UtlBoolean CredentialDB::getCredential ( const Url& uri, const UtlString& realm, UtlString& userid, UtlString& passtoken, UtlString& authType ) const { UtlBoolean found = FALSE; UtlString identity; uri.getIdentity(identity); // Match the row and return the passtoken and authtype if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor; dbQuery query; query="np_identity=",identity, \ "and realm=",realm, \ "order by np_identity asc, realm asc"; if ( cursor.select( query ) > 0 ) { do { userid = cursor->userid; passtoken = cursor->passtoken; authType = cursor->authtype; found = TRUE; } while ( cursor.next() ); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); } return found; } /// Retrieve the User PIN check values for a given identity and realm UtlBoolean CredentialDB::getUserPin ( const Url& uri, const UtlString& realm, UtlString& userid, UtlString& pintoken, UtlString& authType ) const { UtlBoolean found = FALSE; UtlString identity; uri.getIdentity(identity); // Match the row and return the passtoken and authtype if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); dbCursor< CredentialRow > cursor; dbQuery query; query="np_identity=",identity, \ "and realm=",realm, \ "order by np_identity asc, realm asc"; if ( cursor.select( query ) > 0 ) { do { userid = cursor->userid; pintoken = cursor->pintoken; authType = cursor->authtype; found = TRUE; } while ( cursor.next() ); } // Commit rows to memory - multiprocess workaround m_pFastDB->detach(0); } return found; } /// Retrieve the User PIN check values for a given userid and realm UtlBoolean CredentialDB::getUserPin ( const UtlString& userid, const UtlString& realm, Url& uri, UtlString& pintoken, UtlString& authType ) const { UtlBoolean found = FALSE; if ( !userid.isNull() && !realm.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); // Search to see if we have a Credential Row dbCursor< CredentialRow > cursor; dbQuery query; query="userid=",userid,"and realm=",realm; if ( cursor.select(query) > 0 ) { do { uri = cursor->uri; pintoken = cursor->pintoken; authType = cursor->authtype; } while ( cursor.next() ); found = TRUE; } // Commit the rows to memory - multiprocess workaround m_pFastDB->detach(0); } return found; } void CredentialDB::getAllCredentials ( const Url& uri, ResultSet& rResultSet ) const { UtlString identity; uri.getIdentity(identity); // This should erase the contents of the existing resultset rResultSet.clear(); if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); // Search to see if we have a Credential Row dbCursor< CredentialRow > cursor; dbQuery query; query="np_identity=",identity; if ( cursor.select( query ) > 0 ) { do { UtlHashMap record; UtlString* uriValue = new UtlString ( cursor->uri ); UtlString* realmValue = new UtlString ( cursor->realm ); UtlString* useridValue = new UtlString ( cursor->userid ); UtlString* passtokenValue = new UtlString ( cursor->passtoken ); UtlString* pintokenValue = new UtlString ( cursor->pintoken ); UtlString* authtypeValue = new UtlString ( cursor->authtype ); // Memory Leak fixes, make shallow copies of static keys UtlString* uriKey = new UtlString( gUriKey ); UtlString* realmKey = new UtlString( gRealmKey ); UtlString* useridKey = new UtlString( gUseridKey ); UtlString* passtokenKey = new UtlString( gPasstokenKey ); UtlString* pintokenKey = new UtlString( gPintokenKey ); UtlString* authtypeKey = new UtlString( gAuthtypeKey ); record.insertKeyAndValue ( uriKey, uriValue ); record.insertKeyAndValue ( realmKey, realmValue ); record.insertKeyAndValue ( useridKey, useridValue ); record.insertKeyAndValue ( passtokenKey, passtokenValue ); record.insertKeyAndValue ( pintokenKey, pintokenValue ); record.insertKeyAndValue ( authtypeKey, authtypeValue ); rResultSet.addValue(record); } while ( cursor.next() ); } // Commit the rows to memory - multiprocess workaround m_pFastDB->detach(0); } } UtlBoolean CredentialDB::isUriDefined ( const Url& uri, UtlString& realm, UtlString& authType ) const { UtlBoolean found = FALSE; UtlString identity; uri.getIdentity(identity); OsSysLog::add(FAC_DB, PRI_DEBUG, "CredentialDB::isUriDefined identity %s, m_pFastDB=0x%08x ", identity.data(), (int)m_pFastDB); if ( !identity.isNull() && (m_pFastDB != NULL) ) { // Thread Local Storage m_pFastDB->attach(); // Search to see if we have a Credential Row dbCursor< CredentialRow > cursor; dbQuery query; // This is equivalent to select * from credentials // where identity=uri.getIdentity() and realm=realm // and authType = authType query="np_identity=",identity; if ( cursor.select(query) > 0 ) { OsSysLog::add(FAC_DB, PRI_DEBUG, "CredentialDB::isUriDefined cursor selected "); do { realm = cursor->realm; authType = cursor->authtype; } while ( cursor.next() ); found = TRUE; } // Commit the rows to memory - multiprocess workaround m_pFastDB->detach(0); } OsSysLog::add(FAC_DB, PRI_DEBUG, "CredentialDB::isUriDefined found=%d ", (int)found); return found; } CredentialDB* CredentialDB::getInstance( const UtlString& name ) { // Critical Section here OsLock lock( sLockMutex ); // See if this is the first time through for this process // Note that this being null => pgDatabase is also null if ( spInstance == NULL ) { // Create the singleton class for clients to use spInstance = new CredentialDB( name ); } return spInstance; }