diff options
Diffstat (limited to 'libkdepim/ldapclient.cpp')
-rw-r--r-- | libkdepim/ldapclient.cpp | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/libkdepim/ldapclient.cpp b/libkdepim/ldapclient.cpp new file mode 100644 index 000000000..34e8044fb --- /dev/null +++ b/libkdepim/ldapclient.cpp @@ -0,0 +1,599 @@ +/* kldapclient.cpp - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen <hansen@kde.org> + * + * Ported to KABC by Daniel Molkentin <molkentin@kde.org> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + + +#include <qfile.h> +#include <qimage.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qtextstream.h> +#include <qurl.h> + +#include <kabc/ldapurl.h> +#include <kabc/ldif.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdirwatch.h> +#include <kmdcodec.h> +#include <kprotocolinfo.h> +#include <kstandarddirs.h> +#include <kstaticdeleter.h> + +#include "ldapclient.h" + +using namespace KPIM; + +KConfig *KPIM::LdapSearch::s_config = 0L; +static KStaticDeleter<KConfig> configDeleter; + +QString LdapObject::toString() const +{ + QString result = QString::fromLatin1( "\ndn: %1\n" ).arg( dn ); + for ( LdapAttrMap::ConstIterator it = attrs.begin(); it != attrs.end(); ++it ) { + QString attr = it.key(); + for ( LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2 ) { + result += QString::fromUtf8( KABC::LDIF::assembleLine( attr, *it2, 76 ) ) + "\n"; + } + } + + return result; +} + +void LdapObject::clear() +{ + dn = QString::null; + objectClass = QString::null; + attrs.clear(); +} + +void LdapObject::assign( const LdapObject& that ) +{ + if ( &that != this ) { + dn = that.dn; + attrs = that.attrs; + client = that.client; + } +} + +LdapClient::LdapClient( int clientNumber, QObject* parent, const char* name ) + : QObject( parent, name ), mJob( 0 ), mActive( false ), mReportObjectClass( false ) +{ +// d = new LdapClientPrivate; + mClientNumber = clientNumber; + mCompletionWeight = 50 - mClientNumber; +} + +LdapClient::~LdapClient() +{ + cancelQuery(); +// delete d; d = 0; +} + +void LdapClient::setAttrs( const QStringList& attrs ) +{ + mAttrs = attrs; + for ( QStringList::Iterator it = mAttrs.begin(); it != mAttrs.end(); ++it ) + if( (*it).lower() == "objectclass" ){ + mReportObjectClass = true; + return; + } + mAttrs << "objectClass"; // via objectClass we detect distribution lists + mReportObjectClass = false; +} + +void LdapClient::startQuery( const QString& filter ) +{ + cancelQuery(); + KABC::LDAPUrl url; + + url.setProtocol( ( mServer.security() == LdapServer::SSL ) ? "ldaps" : "ldap" ); + if ( mServer.auth() != LdapServer::Anonymous ) { + url.setUser( mServer.user() ); + url.setPass( mServer.pwdBindDN() ); + } + url.setHost( mServer.host() ); + url.setPort( mServer.port() ); + url.setExtension( "x-ver", QString::number( mServer.version() ) ); + url.setDn( mServer.baseDN() ); + url.setDn( mServer.baseDN() ); + if ( mServer.security() == LdapServer::TLS ) url.setExtension( "x-tls","" ); + if ( mServer.auth() == LdapServer::SASL ) { + url.setExtension( "x-sasl","" ); + if ( !mServer.bindDN().isEmpty() ) url.setExtension( "x-bindname", mServer.bindDN() ); + if ( !mServer.mech().isEmpty() ) url.setExtension( "x-mech", mServer.mech() ); + } + if ( mServer.timeLimit() != 0 ) url.setExtension( "x-timelimit", + QString::number( mServer.timeLimit() ) ); + if ( mServer.sizeLimit() != 0 ) url.setExtension( "x-sizelimit", + QString::number( mServer.sizeLimit() ) ); + + url.setAttributes( mAttrs ); + url.setScope( mScope == "one" ? KABC::LDAPUrl::One : KABC::LDAPUrl::Sub ); + url.setFilter( "("+filter+")" ); + + kdDebug(5300) << "LdapClient: Doing query: " << url.prettyURL() << endl; + + startParseLDIF(); + mActive = true; + mJob = KIO::get( url, false, false ); + connect( mJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ), + this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) ); + connect( mJob, SIGNAL( infoMessage( KIO::Job*, const QString& ) ), + this, SLOT( slotInfoMessage( KIO::Job*, const QString& ) ) ); + connect( mJob, SIGNAL( result( KIO::Job* ) ), + this, SLOT( slotDone() ) ); +} + +void LdapClient::cancelQuery() +{ + if ( mJob ) { + mJob->kill(); + mJob = 0; + } + + mActive = false; +} + +void LdapClient::slotData( KIO::Job*, const QByteArray& data ) +{ + parseLDIF( data ); +} + +void LdapClient::slotInfoMessage( KIO::Job*, const QString & ) +{ + //qDebug("Job said \"%s\"", info.latin1()); +} + +void LdapClient::slotDone() +{ + endParseLDIF(); + mActive = false; +#if 0 + for ( QValueList<LdapObject>::Iterator it = mObjects.begin(); it != mObjects.end(); ++it ) { + qDebug( (*it).toString().latin1() ); + } +#endif + int err = mJob->error(); + if ( err && err != KIO::ERR_USER_CANCELED ) { + emit error( mJob->errorString() ); + } + emit done(); +} + +void LdapClient::startParseLDIF() +{ + mCurrentObject.clear(); + mLdif.startParsing(); +} + +void LdapClient::endParseLDIF() +{ +} + +void LdapClient::finishCurrentObject() +{ + mCurrentObject.dn = mLdif.dn(); + const QString sClass( mCurrentObject.objectClass.lower() ); + if( sClass == "groupofnames" || sClass == "kolabgroupofnames" ){ + LdapAttrMap::ConstIterator it = mCurrentObject.attrs.find("mail"); + if( it == mCurrentObject.attrs.end() ){ + // No explicit mail address found so far? + // Fine, then we use the address stored in the DN. + QString sMail; + QStringList lMail = QStringList::split(",dc=", mCurrentObject.dn); + const int n = lMail.count(); + if( n ){ + if( lMail.first().lower().startsWith("cn=") ){ + sMail = lMail.first().simplifyWhiteSpace().mid(3); + if( 1 < n ) + sMail.append('@'); + for( int i=1; i<n; ++i){ + sMail.append( lMail[i] ); + if( i < n-1 ) + sMail.append('.'); + } + mCurrentObject.attrs["mail"].append( sMail.utf8() ); + } + } + } + } + mCurrentObject.client = this; + emit result( mCurrentObject ); + mCurrentObject.clear(); +} + +void LdapClient::parseLDIF( const QByteArray& data ) +{ + //kdDebug(5300) << "LdapClient::parseLDIF( " << QCString(data.data(), data.size()+1) << " )" << endl; + if ( data.size() ) { + mLdif.setLDIF( data ); + } else { + mLdif.endLDIF(); + } + + KABC::LDIF::ParseVal ret; + QString name; + do { + ret = mLdif.nextItem(); + switch ( ret ) { + case KABC::LDIF::Item: + { + name = mLdif.attr(); + // Must make a copy! QByteArray is explicitely shared + QByteArray value = mLdif.val().copy(); + bool bIsObjectClass = name.lower() == "objectclass"; + if( bIsObjectClass ) + mCurrentObject.objectClass = QString::fromUtf8( value, value.size() ); + if( mReportObjectClass || !bIsObjectClass ) + mCurrentObject.attrs[ name ].append( value ); + //kdDebug(5300) << "LdapClient::parseLDIF(): name=" << name << " value=" << QCString(value.data(), value.size()+1) << endl; + } + break; + case KABC::LDIF::EndEntry: + finishCurrentObject(); + break; + default: + break; + } + } while ( ret != KABC::LDIF::MoreData ); +} + +int LdapClient::clientNumber() const +{ + return mClientNumber; +} + +int LdapClient::completionWeight() const +{ + return mCompletionWeight; +} + +void LdapClient::setCompletionWeight( int weight ) +{ + mCompletionWeight = weight; +} + +void LdapSearch::readConfig( LdapServer &server, KConfig *config, int j, bool active ) +{ + QString prefix; + if ( active ) prefix = "Selected"; + QString host = config->readEntry( prefix + QString( "Host%1" ).arg( j ), "" ).stripWhiteSpace(); + if ( !host.isEmpty() ) + server.setHost( host ); + + int port = config->readNumEntry( prefix + QString( "Port%1" ).arg( j ), 389 ); + server.setPort( port ); + + QString base = config->readEntry( prefix + QString( "Base%1" ).arg( j ), "" ).stripWhiteSpace(); + if ( !base.isEmpty() ) + server.setBaseDN( base ); + + QString user = config->readEntry( prefix + QString( "User%1" ).arg( j ) ).stripWhiteSpace(); + if ( !user.isEmpty() ) + server.setUser( user ); + + QString bindDN = config->readEntry( prefix + QString( "Bind%1" ).arg( j ) ).stripWhiteSpace(); + if ( !bindDN.isEmpty() ) + server.setBindDN( bindDN ); + + QString pwdBindDN = config->readEntry( prefix + QString( "PwdBind%1" ).arg( j ) ); + if ( !pwdBindDN.isEmpty() ) + server.setPwdBindDN( pwdBindDN ); + + server.setTimeLimit( config->readNumEntry( prefix + QString( "TimeLimit%1" ).arg( j ) ) ); + server.setSizeLimit( config->readNumEntry( prefix + QString( "SizeLimit%1" ).arg( j ) ) ); + server.setVersion( config->readNumEntry( prefix + QString( "Version%1" ).arg( j ), 3 ) ); + server.setSecurity( config->readNumEntry( prefix + QString( "Security%1" ).arg( j ) ) ); + server.setAuth( config->readNumEntry( prefix + QString( "Auth%1" ).arg( j ) ) ); + server.setMech( config->readEntry( prefix + QString( "Mech%1" ).arg( j ) ) ); +} + +void LdapSearch::writeConfig( const LdapServer &server, KConfig *config, int j, bool active ) +{ + QString prefix; + if ( active ) prefix = "Selected"; + config->writeEntry( prefix + QString( "Host%1" ).arg( j ), server.host() ); + config->writeEntry( prefix + QString( "Port%1" ).arg( j ), server.port() ); + config->writeEntry( prefix + QString( "Base%1" ).arg( j ), server.baseDN() ); + config->writeEntry( prefix + QString( "User%1" ).arg( j ), server.user() ); + config->writeEntry( prefix + QString( "Bind%1" ).arg( j ), server.bindDN() ); + config->writeEntry( prefix + QString( "PwdBind%1" ).arg( j ), server.pwdBindDN() ); + config->writeEntry( prefix + QString( "TimeLimit%1" ).arg( j ), server.timeLimit() ); + config->writeEntry( prefix + QString( "SizeLimit%1" ).arg( j ), server.sizeLimit() ); + config->writeEntry( prefix + QString( "Version%1" ).arg( j ), server.version() ); + config->writeEntry( prefix + QString( "Security%1" ).arg( j ), server.security() ); + config->writeEntry( prefix + QString( "Auth%1" ).arg( j ), server.auth() ); + config->writeEntry( prefix + QString( "Mech%1" ).arg( j ), server.mech() ); +} + +KConfig* LdapSearch::config() +{ + if ( !s_config ) + configDeleter.setObject( s_config, new KConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals + + return s_config; +} + + +LdapSearch::LdapSearch() + : mActiveClients( 0 ), mNoLDAPLookup( false ) +{ + if ( !KProtocolInfo::isKnownProtocol( KURL("ldap://localhost") ) ) { + mNoLDAPLookup = true; + return; + } + + readConfig(); + connect(KDirWatch::self(), SIGNAL(dirty (const QString&)),this, + SLOT(slotFileChanged(const QString&))); +} + +void LdapSearch::readConfig() +{ + cancelSearch(); + QValueList< LdapClient* >::Iterator it; + for ( it = mClients.begin(); it != mClients.end(); ++it ) + delete *it; + mClients.clear(); + + // stolen from KAddressBook + KConfig *config = KPIM::LdapSearch::config(); + config->setGroup( "LDAP" ); + int numHosts = config->readUnsignedNumEntry( "NumSelectedHosts"); + if ( !numHosts ) { + mNoLDAPLookup = true; + } else { + for ( int j = 0; j < numHosts; j++ ) { + LdapClient* ldapClient = new LdapClient( j, this ); + LdapServer server; + readConfig( server, config, j, true ); + if ( !server.host().isEmpty() ) mNoLDAPLookup = false; + ldapClient->setServer( server ); + + int completionWeight = config->readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( j ), -1 ); + if ( completionWeight != -1 ) + ldapClient->setCompletionWeight( completionWeight ); + + QStringList attrs; + // note: we need "objectClass" to detect distribution lists + attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass"; + ldapClient->setAttrs( attrs ); + + connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ), + this, SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) ); + connect( ldapClient, SIGNAL( done() ), + this, SLOT( slotLDAPDone() ) ); + connect( ldapClient, SIGNAL( error( const QString& ) ), + this, SLOT( slotLDAPError( const QString& ) ) ); + + mClients.append( ldapClient ); + } + + connect( &mDataTimer, SIGNAL( timeout() ), SLOT( slotDataTimer() ) ); + } + mConfigFile = locateLocal( "config", "kabldaprc" ); + KDirWatch::self()->addFile( mConfigFile ); +} + +void LdapSearch::slotFileChanged( const QString& file ) +{ + if ( file == mConfigFile ) + readConfig(); +} + +void LdapSearch::startSearch( const QString& txt ) +{ + if ( mNoLDAPLookup ) + return; + + cancelSearch(); + + int pos = txt.find( '\"' ); + if( pos >= 0 ) + { + ++pos; + int pos2 = txt.find( '\"', pos ); + if( pos2 >= 0 ) + mSearchText = txt.mid( pos , pos2 - pos ); + else + mSearchText = txt.mid( pos ); + } else + mSearchText = txt; + + /* The reasoning behind this filter is: + * If it's a person, or a distlist, show it, even if it doesn't have an email address. + * If it's not a person, or a distlist, only show it if it has an email attribute. + * This allows both resource accounts with an email address which are not a person and + * person entries without an email address to show up, while still not showing things + * like structural entries in the ldap tree. */ + QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(mail=*@%3*)(givenName=%4*)(sn=%5*))" ) + .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ); + + QValueList< LdapClient* >::Iterator it; + for ( it = mClients.begin(); it != mClients.end(); ++it ) { + (*it)->startQuery( filter ); + kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl; + ++mActiveClients; + } +} + +void LdapSearch::cancelSearch() +{ + QValueList< LdapClient* >::Iterator it; + for ( it = mClients.begin(); it != mClients.end(); ++it ) + (*it)->cancelQuery(); + + mActiveClients = 0; + mResults.clear(); +} + +void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj ) +{ + mResults.append( obj ); + if ( !mDataTimer.isActive() ) + mDataTimer.start( 500, true ); +} + +void LdapSearch::slotLDAPError( const QString& ) +{ + slotLDAPDone(); +} + +void LdapSearch::slotLDAPDone() +{ + if ( --mActiveClients > 0 ) + return; + + finish(); +} + +void LdapSearch::slotDataTimer() +{ + QStringList lst; + LdapResultList reslist; + makeSearchData( lst, reslist ); + if ( !lst.isEmpty() ) + emit searchData( lst ); + if ( !reslist.isEmpty() ) + emit searchData( reslist ); +} + +void LdapSearch::finish() +{ + mDataTimer.stop(); + + slotDataTimer(); // emit final bunch of data + emit searchDone(); +} + +void LdapSearch::makeSearchData( QStringList& ret, LdapResultList& resList ) +{ + QString search_text_upper = mSearchText.upper(); + + QValueList< KPIM::LdapObject >::ConstIterator it1; + for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) { + QString name, mail, givenname, sn; + QStringList mails; + bool isDistributionList = false; + bool wasCN = false; + bool wasDC = false; + + kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl; + + LdapAttrMap::ConstIterator it2; + for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) { + QByteArray val = (*it2).first(); + int len = val.size(); + if( len > 0 && '\0' == val[len-1] ) + --len; + const QString tmp = QString::fromUtf8( val, len ); + kdDebug(5300) << " key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl; + if ( it2.key() == "cn" ) { + name = tmp; + if( mail.isEmpty() ) + mail = tmp; + else{ + if( wasCN ) + mail.prepend( "." ); + else + mail.prepend( "@" ); + mail.prepend( tmp ); + } + wasCN = true; + } else if ( it2.key() == "dc" ) { + if( mail.isEmpty() ) + mail = tmp; + else{ + if( wasDC ) + mail.append( "." ); + else + mail.append( "@" ); + mail.append( tmp ); + } + wasDC = true; + } else if( it2.key() == "mail" ) { + mail = tmp; + LdapAttrValue::ConstIterator it3 = it2.data().begin(); + for ( ; it3 != it2.data().end(); ++it3 ) { + mails.append( QString::fromUtf8( (*it3).data(), (*it3).size() ) ); + } + } else if( it2.key() == "givenName" ) + givenname = tmp; + else if( it2.key() == "sn" ) + sn = tmp; + else if( it2.key() == "objectClass" && + (tmp == "groupOfNames" || tmp == "kolabGroupOfNames") ) { + isDistributionList = true; + } + } + + if( mails.isEmpty()) { + if ( !mail.isEmpty() ) mails.append( mail ); + if( isDistributionList ) { + kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl; + ret.append( name ); + // following lines commented out for bugfixing kolab issue #177: + // + // Unlike we thought previously we may NOT append the server name here. + // + // The right server is found by the SMTP server instead: Kolab users + // must use the correct SMTP server, by definition. + // + //mail = (*it1).client->base().simplifyWhiteSpace(); + //mail.replace( ",dc=", ".", false ); + //if( mail.startsWith("dc=", false) ) + // mail.remove(0, 3); + //mail.prepend( '@' ); + //mail.prepend( name ); + //mail = name; + } else { + kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl; + continue; // nothing, bad entry + } + } else if ( name.isEmpty() ) { + kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl; + ret.append( mail ); + } else { + kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\" mail: \"" << mail << "\"" << endl; + ret.append( QString( "%1 <%2>" ).arg( name ).arg( mail ) ); + } + + LdapResult sr; + sr.clientNumber = (*it1).client->clientNumber(); + sr.completionWeight = (*it1).client->completionWeight(); + sr.name = name; + sr.email = mails; + resList.append( sr ); + } + + mResults.clear(); +} + +bool LdapSearch::isAvailable() const +{ + return !mNoLDAPLookup; +} + + +#include "ldapclient.moc" |