// // // 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 "net/Url.h" #include "net/HttpMessage.h" #include "net/NetMd5Codec.h" #include "sipdb/SIPDBManager.h" #include "sipdb/ResultSet.h" #include "sipdb/AliasDB.h" #include "sipdb/ExtensionDB.h" #include "sipdb/CredentialDB.h" #include "sipdb/PermissionDB.h" #include "sipdb/SIPXAuthHelper.h" // STATIC INITIALIZERS SIPXAuthHelper* SIPXAuthHelper::spInstance = NULL; OsMutex SIPXAuthHelper::sLockMutex (OsMutex::Q_FIFO); /* ============================ CREATORS ================================== */ SIPXAuthHelper::SIPXAuthHelper() {} SIPXAuthHelper::~SIPXAuthHelper() { OsLock lock( sLockMutex ); // force an imdb close delete SIPDBManager::getInstance(); spInstance = NULL; } SIPXAuthHelper* SIPXAuthHelper::getInstance() { // Critical Section here OsLock lock( sLockMutex ); if ( spInstance == NULL ) { // Create the singleton class for clients to use spInstance = new SIPXAuthHelper(); } return spInstance; } UtlBoolean SIPXAuthHelper::isAuthorizedUser ( const UtlString& loginString, const UtlString& loginPassToken, const UtlString& realmName, const UtlString& domainName, const UtlBoolean& checkPermissions, UtlString& rContactUserID, UtlString& rContactDomain, UtlString& rErrorLog) const { OsStatus result = OS_SUCCESS; UtlString userOrExtensionAtOptDomain(loginString); int sipIndex = userOrExtensionAtOptDomain.index("sip:"); if (((unsigned int)(sipIndex)) != UTL_NOT_FOUND ) { // see if we're being passed in a full URL in which // case strip it down to its identity, discarding the display name if ( sipIndex > 0 ) { Url loginUrl ( userOrExtensionAtOptDomain ); loginUrl.getIdentity( userOrExtensionAtOptDomain ); } else { // sip: is in first position userOrExtensionAtOptDomain = userOrExtensionAtOptDomain( 4, userOrExtensionAtOptDomain.length() - 4 ); } } // we're going to need this irrespective of whether // the user is logging in via mailboxIdentity or extension ResultSet extensions; // Search the credentials database for a // match against the userid column UtlString dbRealm, dbAuthType, dbPassToken; Url mailboxUrl; bool keepsearching = TRUE; // Infinite loop detector int loopCount = 0; while ( keepsearching && ( result != OS_FAILED ) && (loopCount < 3) ) { // first try searching the credentials database using the login string UtlBoolean credentialRecordFound = CredentialDB::getInstance()->getUserPin ( userOrExtensionAtOptDomain, // IN realmName, // IN mailboxUrl, // OUT dbPassToken, // OUT dbAuthType ); // OUT if ( !credentialRecordFound ) { rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - " + (UtlString) "unable to find userOrExtension-" + userOrExtensionAtOptDomain + " in Credential DB\n"; // Since userid does not expect any domain name associated with it, strip the domain off // and try again int atIndex = userOrExtensionAtOptDomain.index("@"); if ( (unsigned int) (atIndex) != UTL_NOT_FOUND ) { userOrExtensionAtOptDomain = userOrExtensionAtOptDomain(0, atIndex); } rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser -" + (UtlString)" retrying credentials using - " + userOrExtensionAtOptDomain + (UtlString)"\n"; // search again adding with a realm in the search credentialRecordFound = CredentialDB::getInstance()->getUserPin ( userOrExtensionAtOptDomain, // IN realmName, // IN mailboxUrl, // OUT dbPassToken, // OUT dbAuthType ); // OUT } if ( credentialRecordFound ) { rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser -" + (UtlString)" Found Credential record for -" + userOrExtensionAtOptDomain + (UtlString)" mailboxUrl " + mailboxUrl.toString() + (UtlString)"\n"; // Fill out the return values mailboxUrl.getUserId(rContactUserID); mailboxUrl.getHostAddress(rContactDomain); // Exit the search loop keepsearching = FALSE; if ( checkPermissions ) { // found a match! now check for voicemail permissions // against the mailboxUrl that was returned above ResultSet permissions; PermissionDB::getInstance()-> getPermissions( mailboxUrl, permissions ); if ( permissions.getSize() > 0 ) { // Ensure the user has the right permissions before proceeding UtlBoolean permissionFound = FALSE; UtlString permissionKey ("permission"); for ( int i=0; i getUri ( userOrExtensionAtOptDomain, mailboxUrl ); if ( !mailboxUrlFound ) { // searching using the userOrExtensionAtOptDomain did // not work, search again (making sure to add/remove // the domain portion) if ( userOrExtensionAtOptDomain.index("@") == UTL_NOT_FOUND ) { userOrExtensionAtOptDomain = userOrExtensionAtOptDomain + "@" + domainName; } else if ( loginString.index( domainName ) != UTL_NOT_FOUND ) { userOrExtensionAtOptDomain = userOrExtensionAtOptDomain(0, userOrExtensionAtOptDomain.index(domainName) -1 ); } rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - " + (UtlString) "Retrying with/(out) realm - " + userOrExtensionAtOptDomain + " in ExtensionDB\n"; // search again adding with a realm in the search mailboxUrlFound = ExtensionDB::getInstance()->getUri ( userOrExtensionAtOptDomain, mailboxUrl ); if (!mailboxUrlFound) { // Write to the log and exit the loop. rErrorLog += "SIPXAuthHelper::isAuthorizedUser - " "No contact found in extensions, Searching AliasDB for Unique Row\n"; // Ensure that we construct a valid Url to search the AliasDB // Aliases have always had a full user@domain Url aliasIdentityUrl; if (loginString.index("@") != UTL_NOT_FOUND) aliasIdentityUrl = loginString; else aliasIdentityUrl = loginString + "@" + domainName; UtlString contactKey ("contact"); ResultSet aliasContacts; AliasDB::getInstance()-> getContacts( aliasIdentityUrl, aliasContacts ); int numRows = aliasContacts.getSize(); if ( numRows != 1 ) { if ( numRows == 0 ) { rErrorLog += "SIPXAuthHelper::isAuthorizedUser - ERROR: Failed to find an alias match for: [" + aliasIdentityUrl.toString() + "] in AliasDB\n"; } else { rErrorLog += "SIPXAuthHelper::isAuthorizedUser - ERROR: Multiple aliases for: [" + aliasIdentityUrl.toString() + "] in AliasDB\n"; } result = OS_FAILED; } else // Successful match { UtlHashMap record; aliasContacts.getIndex(0, record); mailboxUrl = *((UtlString*)record.findValue(&contactKey)); } } else // mailboxUrl is now valid { // update the userOrExtensionAtOptDomain CredentialDB::getInstance()-> getUserPin ( mailboxUrl, // IN realmName, // IN userOrExtensionAtOptDomain, // OUT dbPassToken, // OUT dbAuthType ); // OUT } } else { rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - " + (UtlString) "Found entry in Extensions for -" + userOrExtensionAtOptDomain + (UtlString)" returns " + mailboxUrl.toString() + (UtlString)"\n"; // found the mailbox url associated with the extension now // we need to query the userid from the DB and try again if ( !CredentialDB::getInstance()->getUserPin ( mailboxUrl, // IN realmName, // IN userOrExtensionAtOptDomain, // OUT update this for the next time round the loop dbPassToken, // OUT dbAuthType ) ) // OUT { // Write to the log and exit the loop. rErrorLog += "SIPXAuthHelper::isAuthorizedUser - FAILED - to find entry in Credential DB for " + mailboxUrl.toString(); result = OS_FAILED; } } } loopCount += 1; } // See if we've exceeded the loop count, which could happen if the IMDB tables // have a recurcive relationship, extension's contact URI is not in contacts for ex. if ( loopCount > 2 ) { rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - ERROR ExtensionDB does not have a valid CredentialDB Contact!\n"; result = OS_FAILED; } return(result == OS_SUCCESS); } UtlBoolean SIPXAuthHelper::comparePassToken ( const UtlString& userOrExtensionAtOptDomain, const UtlString& loginPassToken, const UtlString& realmName, const UtlString& dbPassToken, const UtlString& dbAuthType) const { UtlBoolean result = FALSE; // unless it is digest encoded if ( dbAuthType.compareTo("DIGEST", UtlString::ignoreCase) == 0 ) { UtlString compareToToken; if ( loginPassToken.length() != MD5_DIGEST_LENGTH ) { // we have to create the MD5 Digest UtlString compareToToken; UtlString textToEncode = userOrExtensionAtOptDomain + ":" + realmName + ":" + loginPassToken; NetMd5Codec::encode( textToEncode, compareToToken ); if ( dbPassToken.compareTo ( compareToToken ) == 0 ) result = TRUE; } else // potentially MD5 encoded password as length match { if ( loginPassToken.compareTo ( dbPassToken ) != 0 ) { // length matched but failed comparison // likely a paranoid user! Hash their 32 byte PW // we have to create the MD5 Digest UtlString textToEncode = userOrExtensionAtOptDomain + ":" + realmName + ":" + loginPassToken; NetMd5Codec::encode( textToEncode, compareToToken ); if ( dbPassToken.compareTo ( compareToToken ) == 0 ) result = TRUE; } else //passwords match! no need to do anything { result = TRUE; } } } return result; }