//
//
// 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<permissions.getSize(); i++ )
{
UtlHashMap record;
permissions.getIndex( i, record );
UtlString permission = *((UtlString*)record.findValue(&permissionKey));
// AutoAttendant permission is never used here, it is only
// used in creating the dialbyname database
if ( permission.compareTo( "Voicemail", UtlString::ignoreCase )==0 )
{
permissionFound = TRUE;
// unless it is digest encoded
if ( dbAuthType.compareTo("DIGEST", UtlString::ignoreCase) == 0 )
{
if (!comparePassToken (userOrExtensionAtOptDomain,
loginPassToken,
realmName,
dbPassToken,
dbAuthType)) result = OS_FAILED;
}
keepsearching = FALSE;
// break out of for loop
break;
}
}
// it is possible that even though there is an uri defined
// and the passwords match, the user may have insufficient priveleges
if( permissionFound == FALSE )
{
// Write to the log and exit the loop.
rErrorLog +=
(UtlString) "SIPXAuthHelper::isAuthorizedUser - " +
(UtlString)"Voicemail Permission missing for - " + mailboxUrl.toString() +
(UtlString)"\n";
result = OS_FAILED;
}
} else // we have a credential match but no voicemail permission
{
// Write to the log and exit the loop.
rErrorLog +=
(UtlString)"SIPXAuthHelper::isAuthorizedUser - " +
(UtlString)"No Permissions for - " + mailboxUrl.toString() +
(UtlString)"\n";
result = OS_FAILED;
}
}
else if (!comparePassToken (userOrExtensionAtOptDomain,
loginPassToken,
realmName,
dbPassToken,
dbAuthType))
{
// Write to the log and exit the loop.
rErrorLog +=
(UtlString) "SIPXAuthHelper::isAuthorizedUser - " +
(UtlString)"password does not match for - " + mailboxUrl.toString() +
(UtlString)"\n";
result = OS_FAILED;
}
} else // this may be an alias or a mailbox extension
{
rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - " +
(UtlString) "failed to find - " + userOrExtensionAtOptDomain + (UtlString)" in Credential DB\n";
rErrorLog += (UtlString) "SIPXAuthHelper::isAuthorizedUser - " +
(UtlString) "Searching the ExtensionDB for a match for - " + loginString + (UtlString)"\n";
// search for a mailbox URL (note that the sip: has
// been stripped from the userOrExtensionAtOptDomain string
UtlBoolean mailboxUrlFound =
ExtensionDB::getInstance()->
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;
}
syntax highlighted by Code2HTML, v. 0.9.1