CLDAPConnection.cpp [plain text]
#include "CLDAPConnection.h"
#include "CLDAPNodeConfig.h"
#include "CLDAPReplicaInfo.h"
#include "CCachePlugin.h"
#include <DirectoryServiceCore/PrivateTypes.h>
#include <DirectoryService/DirServicesConst.h>
#include <DirectoryServiceCore/CLog.h>
#include <sasl/sasl.h>
#include <syslog.h>
extern CCachePlugin *gCacheNode;
extern DSMutexSemaphore *gKerberosMutex;
CLDAPConnection::CLDAPConnection( CLDAPNodeConfig *inNodeConfig, CLDAPReplicaInfo *inCurrentReplica )
{
fNodeConfig = (inNodeConfig != NULL ? inNodeConfig->Retain() : NULL);
fReplicaInUse = (inCurrentReplica != NULL ? inCurrentReplica->Retain() : NULL);
fbAuthenticated = false;
fbKerberosAuthenticated = false;
fHost = NULL;
fKerberosCache = NULL;
fWriteable = false;
fbBadCredentials = false;
fKerberosID = NULL;
fLDAPUsername = NULL;
fLDAPPassword = NULL;
fLDAPRecordType = NULL;
fIdleCount = 0;
fReachabilityList = NULL;
fConnectionStatus = kConnectionSafe;
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
const char *fileName = __FILE__;
char *offset = strstr(fileName, "/PlugIns/");
if ( offset != NULL )
fPrefixLen = (offset - fileName) + 1;
else
fPrefixLen = 0;
#endif
}
CLDAPConnection::~CLDAPConnection( void )
{
fMutex.WaitLock();
if ( fNodeConfig != NULL )
{
if ( fLDAPUsername != NULL )
DbgLog( kLogPlugin, "CLDAPConnection::~CLDAPConnection - Connection to %s:%s no longer in use - deleted", fNodeConfig->fNodeName,
fLDAPUsername );
else
DbgLog( kLogPlugin, "CLDAPConnection::~CLDAPConnection - Connection to %s no longer in use - deleted", fNodeConfig->fNodeName );
}
else
{
DbgLog( kLogPlugin, "CLDAPConnection::~CLDAPConnection - Connection to Configure Node no longer in use - deleted" );
}
DSRelease( fNodeConfig );
DSFree( fKerberosID );
DSFree( fLDAPUsername );
DSFree( fLDAPPassword );
DSFree( fLDAPRecordType );
DSRelease( fReplicaInUse );
if ( fHost != NULL )
{
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
if ( fKerberosCache != NULL )
{
gKerberosMutex->WaitLock();
krb5_context krbContext = NULL;
krb5_ccache krbCache = NULL;
krb5_error_code retval = 0;
retval = krb5_init_context( &krbContext );
if ( retval == 0 )
retval = krb5_cc_resolve( krbContext, fKerberosCache, &krbCache);
if ( retval == 0 )
krb5_cc_destroy( krbContext, krbCache );
DSFree( fKerberosCache );
gKerberosMutex->SignalLock();
}
fReachabilityList = NULL;
fMutex.SignalLock();
}
CLDAPConnection *CLDAPConnection::CreateCopy( void )
{
CLDAPConnection *newConnection = NULL;
if ( fConnectionStatus == kConnectionSafe )
newConnection = new CLDAPConnection( fNodeConfig, fReplicaInUse );
return newConnection;
}
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
LDAP *CLDAPConnection::LockLDAPSessionDebug( char *inFile, int inLine )
#else
LDAP *CLDAPConnection::LockLDAPSession( void )
#endif
{
if ( fConnectionStatus == kConnectionUnsafe ||
fNodeConfig == NULL ||
fNodeConfig->fConfigDeleted == true ||
fNodeConfig->fEnableUse == false )
{
return NULL;
}
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY)
fMutex.WaitDebug( inFile, inLine );
#else
fMutex.WaitLock();
#endif
if ( fHost != NULL )
{
if ( fConnectionStatus != kConnectionSafe )
{
DbgLog( kLogDebug, "CLDAPConnection::LockLDAPSession - closing existing session - state is not safe" );
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
else
{
fNodeConfig->UpdateDynamicData( fHost, fReplicaInUse );
}
}
if ( fHost == NULL && fbBadCredentials == false )
{
tDirStatus dsStatus;
if ( fbAuthenticated )
fHost = fNodeConfig->EstablishConnection( &fReplicaInUse, fWriteable, fLDAPUsername, fKerberosID, fLDAPPassword,
(void *) LDAPFrameworkCallback, (void *) this, &dsStatus );
else if ( fbKerberosAuthenticated )
fHost = fNodeConfig->EstablishConnection( &fReplicaInUse, fWriteable, fKerberosCache, (void *) LDAPFrameworkCallback,
(void *) this, &dsStatus );
else
fHost = fNodeConfig->EstablishConnection( &fReplicaInUse, fWriteable, (void *) LDAPFrameworkCallback, (void *) this, &dsStatus );
if ( fHost == NULL && (fbAuthenticated == true || fbKerberosAuthenticated == true) && dsStatus == eDSAuthFailed )
fbBadCredentials = true;
}
if ( fHost != NULL )
{
fIdleCount = 0;
SetConnectionStatus( kConnectionSafe );
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
DbgLog( kLogDebug, "CLDAPConnection::LockLDAPSession - %s:%d locked session handle %X", inFile + fPrefixLen, inLine, fHost );
#endif
return fHost;
}
SetConnectionStatus( kConnectionUnsafe );
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY)
fMutex.SignalDebug( inFile, inLine );
#else
fMutex.SignalLock();
#endif
return NULL;
}
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
void CLDAPConnection::UnlockLDAPSessionDebug( LDAP * &inLDAP, bool inFailed, char *inFile, int inLine )
#else
void CLDAPConnection::UnlockLDAPSession( LDAP * &inLDAP, bool inFailed )
#endif
{
if ( inLDAP != NULL )
{
if ( inLDAP == fHost )
{
if ( inFailed == true )
{
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
if ( fReplicaInUse != NULL ) fReplicaInUse->ConnectionFailed();
SetConnectionStatus( kConnectionUnknown );
}
inLDAP = NULL;
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
DbgLog( kLogDebug, "CLDAPConnection::UnlockLDAPSession - %s:%d unlocked session handle %X", inFile + fPrefixLen, inLine, fHost );
#endif
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY)
fMutex.SignalDebug( inFile, inLine);
#else
fMutex.SignalLock();
#endif
}
else
{
#if defined(DEBUG_LOCKS) || defined(DEBUG_LOCKS_HISTORY) || defined(DEBUG_LDAPSESSION_LOCKS)
DbgLog( kLogDebug, "CLDAPConnection::UnlockLDAPSession - %s:%d attempted to unlock session handle %X != %X", inFile + fPrefixLen, inLine,
inLDAP, fHost );
syslog( LOG_CRIT, "LDAPv3 - %s:%d attempted to unlock session handle 0x%08x != 0x%08x", inFile + fPrefixLen, inLine, inLDAP, fHost );
#else
syslog( LOG_CRIT, "LDAPv3: Attempt to unlock LDAP session not owned by this object" );
#endif
abort();
}
}
}
UInt32 CLDAPConnection::SessionSecurityLevel( LDAP *inLDAP )
{
UInt32 iSecurityLevel = 0;
if ( inLDAP != NULL )
{
sasl_ssf_t negotiatedSSF;
if ( fNodeConfig->fIsSSL )
iSecurityLevel |= (kSecDisallowCleartext | kSecPacketEncryption);
if ( ldap_get_option(inLDAP, LDAP_OPT_X_SASL_SSF, &negotiatedSSF) == 0 )
{
if ( negotiatedSSF >= 56 )
iSecurityLevel |= (kSecDisallowCleartext | kSecPacketSigning | kSecPacketEncryption | kSecManInMiddle);
else if ( negotiatedSSF >= 1 )
iSecurityLevel |= (kSecDisallowCleartext | kSecPacketSigning | kSecManInMiddle);
}
}
return iSecurityLevel;
}
void CLDAPConnection::PeriodicTask( void )
{
if ( fMutex.WaitTryLock() )
{
if ( fHost != NULL )
{
if ( fConnectionStatus != kConnectionSafe || (++fIdleCount) > fNodeConfig->fIdleMaxCount )
{
if ( fbAuthenticated )
DbgLog( kLogPlugin, "CLDAPConnection::PeriodicTask - Status Node: %s:%s -- Server: %s -- %sDisconnected",
fNodeConfig->fNodeName, fLDAPUsername, fReplicaInUse->fIPAddress,
(fConnectionStatus == kConnectionSafe ? "idle " : "") );
else
DbgLog( kLogPlugin, "CLDAPConnection::PeriodicTask - Status Node: %s -- Server: %s -- %sDisconnected",
fNodeConfig->fNodeName, fReplicaInUse->fIPAddress,
(fConnectionStatus == kConnectionSafe ? "idle " : "") );
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
else
{
if ( fbAuthenticated )
DbgLog( kLogDebug, "CLDAPConnection::PeriodicTask - Status Node: %s:%s -- Server: %s -- Time: %d sec -- Idle",
fNodeConfig->fNodeName, fLDAPUsername, fReplicaInUse->fIPAddress, fIdleCount * 30 );
else
DbgLog( kLogDebug, "CLDAPConnection::PeriodicTask - Status Node: %s -- Server: %s -- Time: %d sec -- Idle", fNodeConfig->fNodeName,
fReplicaInUse->fIPAddress, fIdleCount * 30 );
}
}
fMutex.SignalLock();
}
}
void CLDAPConnection::NetworkTransition( void )
{
OSAtomicCompareAndSwap32Barrier( kConnectionUnsafe, kConnectionUnknown, &fConnectionStatus );
}
tDirStatus CLDAPConnection::Authenticate( const char *inLDAPUsername, const char *inRecordType, const char *inKerberosID, const char *inPassword )
{
tDirStatus dsStatus = eDSAuthFailed;
if ( inLDAPUsername != NULL && inPassword != NULL )
{
fbAuthenticated = true;
fLDAPUsername = strdup( inLDAPUsername );
fLDAPRecordType = (inRecordType != NULL ? strdup(inRecordType) : strdup(kDSStdRecordTypeUsers));
if ( inKerberosID != NULL ) fKerberosID = strdup( inKerberosID );
fLDAPPassword = strdup( inPassword );
fMutex.WaitLock();
if ( fHost != NULL )
{
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
fMutex.SignalLock();
LDAP *pTempLD = LockLDAPSession();
if ( pTempLD != NULL )
{
dsStatus = eDSNoErr;
UnlockLDAPSession( pTempLD, false );
}
}
return dsStatus;
}
tDirStatus CLDAPConnection::AuthenticateKerberos( const char *inUsername, const char *inRecordType, krb5_creds *inCredsPtr, const char *inKerberosID )
{
if ( inUsername == NULL || inCredsPtr == NULL || inKerberosID == NULL )
return eDSNullParameter;
if ( fReplicaInUse == NULL || fReplicaInUse->SupportsSASLMethod(CFSTR("GSSAPI")) == false )
return eDSAuthMethodNotSupported;
tDirStatus dsStatus = eDSAuthFailed;
krb5_context krbContext = NULL;
krb5_principal principal = NULL;
krb5_ccache krbCache = NULL;
krb5_error_code retval = 0;
char *principalString = NULL;
gKerberosMutex->WaitLock();
do
{
retval = krb5_init_context( &krbContext );
if ( retval ) break;
retval = krb5_parse_name( krbContext, inUsername, &principal );
if ( retval ) break;
retval = krb5_unparse_name( krbContext, principal, &principalString );
if ( retval ) break;
retval = krb5_cc_new_unique( krbContext, "MEMORY", inUsername, &krbCache );
if ( retval ) break;
const char *cacheName = krb5_cc_get_name( krbContext, krbCache );
int length = sizeof("MEMORY:") + strlen(cacheName) + 1;
fKerberosCache = (char *) calloc( length, sizeof(char) );
strlcpy( fKerberosCache, "MEMORY:", length );
strlcat( fKerberosCache, cacheName, length );
retval = krb5_cc_resolve( krbContext, fKerberosCache, &krbCache);
if ( retval ) break;
retval = krb5_cc_set_default_name( krbContext, fKerberosCache );
if ( retval ) break;
retval = krb5_cc_initialize( krbContext, krbCache, principal );
if ( retval ) break;
DbgLog( kLogDebug, "CLDAPConnection::AuthenticateKerberos - Initialized cache <%s> for user <%s>", principalString );
retval = krb5_cc_store_cred( krbContext, krbCache, inCredsPtr );
if ( retval ) break;
fbKerberosAuthenticated = true;
fLDAPUsername = strdup( inUsername );
fLDAPRecordType = (inRecordType != NULL ? strdup(inRecordType) : strdup(kDSStdRecordTypeUsers));
fKerberosID = strdup( inKerberosID );
fMutex.WaitLock();
if ( fHost != NULL )
{
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
fMutex.SignalLock();
LDAP *pTempLD = LockLDAPSession();
if ( pTempLD != NULL )
{
dsStatus = eDSNoErr;
UnlockLDAPSession( pTempLD, false );
} else {
krbCache = NULL;
DSFree( fKerberosCache );
}
} while ( 0 );
if ( principalString != NULL )
{
krb5_free_unparsed_name( krbContext, principalString );
principalString = NULL;
}
if ( principal != NULL )
{
krb5_free_principal( krbContext, principal );
principal = NULL;
}
if ( krbCache != NULL )
{
if ( eDSNoErr != dsStatus )
{
krb5_cc_destroy( krbContext, krbCache );
krbCache = NULL;
}
else
{
krb5_cc_close( krbContext, krbCache );
krbCache = NULL;
}
}
if ( inCredsPtr != NULL )
{
krb5_free_cred_contents( krbContext, inCredsPtr );
inCredsPtr = NULL;
}
if ( krbContext != NULL )
{
krb5_free_context( krbContext );
krbContext = NULL;
}
gKerberosMutex->SignalLock();
return dsStatus;
}
void CLDAPConnection::UpdateCredentials( const char *inPassword )
{
if ( fbAuthenticated == true && inPassword != NULL )
{
DSFree( fLDAPPassword );
fLDAPPassword = strdup( inPassword );
}
}
void CLDAPConnection::CheckFailed( void )
{
if ( fNodeConfig != NULL && fNodeConfig->CheckIfFailed() == false )
{
SetConnectionStatus( kConnectionSafe );
}
}
char *CLDAPConnection::CopyReplicaIPAddress( void )
{
char *returnValue = NULL;
fMutex.WaitLock();
if ( fReplicaInUse != NULL )
returnValue = strdup( fReplicaInUse->fIPAddress );
fMutex.SignalLock();
return returnValue;
}
char *CLDAPConnection::CopyReplicaServicePrincipal( void )
{
char *returnValue = NULL;
fMutex.WaitLock();
if ( fReplicaInUse != NULL )
returnValue = fReplicaInUse->CopyServicePrincipal();
fMutex.SignalLock();
return returnValue;
}
void CLDAPConnection::SetConnectionStatus( int32_t inStatus )
{
int32_t oldStatus = fConnectionStatus;
fConnectionStatus = inStatus;
OSMemoryBarrier();
if ( oldStatus != inStatus )
{
if ( fNodeConfig != NULL )
{
char nodeName[256];
strlcpy( nodeName, "/LDAPv3/", sizeof(nodeName) );
strlcat( nodeName, fNodeConfig->fNodeName, sizeof(nodeName) );
if ( gCacheNode != NULL )
gCacheNode->UpdateNodeReachability( nodeName, (inStatus == kConnectionSafe) );
}
}
}
void CLDAPConnection::ReachabilityCallback( SCNetworkReachabilityRef inTarget, SCNetworkConnectionFlags inFlags, void *inInfo )
{
CLDAPConnection *pLDAPConnection = (CLDAPConnection *) inInfo;
pLDAPConnection->ReachabilityNotification( inTarget, inFlags );
}
void CLDAPConnection::LDAPFrameworkCallback( LDAP *inLD, int inDesc, int inOpening, void *inParams )
{
CLDAPConnection *pLDAPConnection = (CLDAPConnection *) inParams;
if ( inOpening )
pLDAPConnection->StartReachability( inDesc );
else
pLDAPConnection->StopReachability( inDesc );
}
void CLDAPConnection::ReachabilityNotification( SCNetworkReachabilityRef inTarget, SCNetworkConnectionFlags inFlags )
{
if ( (inFlags & kSCNetworkFlagsReachable) == 0 || (inFlags & kSCNetworkFlagsConnectionRequired) != 0 )
{
sLDAPReachabilityList *prevPtr;
sLDAPReachabilityList *currPtr;
char nodeName[256];
strlcpy( nodeName, "/LDAPv3/", sizeof(nodeName) );
strlcat( nodeName, fNodeConfig->fNodeName, sizeof(nodeName) );
fReachabilityLock.WaitLock();
for (prevPtr = NULL, currPtr = fReachabilityList; currPtr != NULL; prevPtr = currPtr, currPtr = currPtr->next)
{
if ( currPtr->reachabilityRef == inTarget )
{
SCNetworkReachabilityUnscheduleFromRunLoop( currPtr->reachabilityRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode );
CFRelease( currPtr->reachabilityRef );
if (prevPtr != NULL)
prevPtr->next = currPtr->next;
else fReachabilityList = currPtr->next;
DbgLog( kLogDebug, "CLDAPConnection::ReachabilityNotification - %s -> %s for node %s is no longer reachable", currPtr->srcIP,
currPtr->dstIP, nodeName );
delete currPtr; break;
}
}
if ( fReachabilityList == NULL )
{
SetConnectionStatus( kConnectionUnknown );
if ( fReplicaInUse != NULL ) fReplicaInUse->ConnectionFailed();
}
fReachabilityLock.SignalLock();
if ( fMutex.WaitTryLock() )
{
if ( fHost != NULL )
{
DbgLog( kLogPlugin, "CLDAPConnection::ReachabilityNotification - %s - closed LDAP session - not in use",
nodeName );
ldap_unbind_ext_s( fHost, NULL, NULL );
fHost = NULL;
}
fMutex.SignalLock();
}
else if ( fHost != NULL ) {
DbgLog( kLogPlugin, "CLDAPConnection::ReachabilityNotification - %s - unable to close LDAP session - in use",
nodeName );
}
}
}
void CLDAPConnection::StartReachability( int inSocket )
{
if ( inSocket >= 0 )
{
sockaddr_storage localStorage;
sockaddr_storage peerStorage;
sockaddr *localAddr = (sockaddr *) &localStorage;
sockaddr *peerAddr = (sockaddr *) &peerStorage;
socklen_t localLen = sizeof(sockaddr_storage);
socklen_t peerLen = sizeof(sockaddr_storage);
if ( 0 != getsockname(inSocket, localAddr, &localLen) )
{
localAddr = NULL;
DbgLog( kLogError, "CLDAPConnection::StartReachability - could not get local sockaddr info for %d", inSocket );
}
if ( 0 != getpeername(inSocket, peerAddr, &peerLen) )
{
peerAddr = NULL;
DbgLog( kLogError, "CLDAPConnection::StartReachability - could not get peer sockaddr info for %d", inSocket );
}
if (localAddr != NULL && peerAddr != NULL)
{
SCNetworkReachabilityRef scReachRef;
scReachRef = SCNetworkReachabilityCreateWithAddressPair( kCFAllocatorDefault, localAddr, peerAddr );
if (scReachRef != NULL)
{
SCNetworkReachabilityContext reachabilityContext = { 0, NULL, NULL, NULL, NULL };
sLDAPReachabilityList *newReachItem = new sLDAPReachabilityList;
newReachItem->reachabilityRef = scReachRef;
newReachItem->socket = inSocket;
newReachItem->srcIP[0] = '\0';
newReachItem->dstIP[0] = '\0';
inet_ntop( localAddr->sa_family, (char *)(localAddr) + (localAddr->sa_family == AF_INET6 ? 8 : 4), newReachItem->srcIP,
sizeof(newReachItem->srcIP) );
inet_ntop( peerAddr->sa_family, (char *)(peerAddr) + (peerAddr->sa_family == AF_INET6 ? 8 : 4), newReachItem->dstIP,
sizeof(newReachItem->dstIP) );
reachabilityContext.info = this;
if (SCNetworkReachabilitySetCallback(scReachRef, ReachabilityCallback, &reachabilityContext) == FALSE)
{
DbgLog( kLogError, "CLDAPConnection::StartReachability - unable to set callback for SCNetworkReachabilityRef for socket %d",
inSocket );
}
else if (SCNetworkReachabilityScheduleWithRunLoop(scReachRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode) == FALSE)
{
DbgLog( kLogError, "CLDAPConnection::StartReachability - unable to schedule SCNetworkReachabilityRef with Runloop for socket %d",
inSocket );
}
else
{
DbgLog( kLogDebug, "CLDAPConnection::StartReachability - watching socket = %d, %s -> %s", inSocket, newReachItem->srcIP,
newReachItem->dstIP );
fReachabilityLock.WaitLock();
newReachItem->next = fReachabilityList; fReachabilityList = newReachItem;
fReachabilityLock.SignalLock();
newReachItem = NULL;
}
if ( newReachItem != NULL )
{
delete newReachItem;
newReachItem = NULL;
DSCFRelease( scReachRef );
}
}
else
{
DbgLog( kLogError, "CLDAPConnection::StartReachability - unable to watch socket = %d", inSocket );
}
}
}
else
{
DbgLog( kLogError, "CLDAPConnection::StartReachability - unable to watch socket = %d", inSocket );
}
}
void CLDAPConnection::StopReachability( int inSocket )
{
sLDAPReachabilityList *prevPtr;
sLDAPReachabilityList *currPtr;
fReachabilityLock.WaitLock();
for (prevPtr = NULL, currPtr = fReachabilityList; currPtr != NULL; prevPtr = currPtr, currPtr = currPtr->next)
{
if ( currPtr->socket == inSocket )
{
if ( prevPtr != NULL )
prevPtr->next = currPtr->next;
else fReachabilityList = currPtr->next;
if ( currPtr->reachabilityRef != NULL )
{
SCNetworkReachabilityUnscheduleFromRunLoop( currPtr->reachabilityRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode );
CFRelease( currPtr->reachabilityRef );
DbgLog( kLogDebug, "CLDAPConnection::StopReachability - no longer watching socket = %d", inSocket );
}
delete currPtr; currPtr = NULL;
break;
}
}
fReachabilityLock.SignalLock();
}