libkdepim Library API Documentation

ldapclient.cpp

00001 /* kldapclient.cpp - LDAP access
00002  *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
00003  *
00004  *      Author: Steffen Hansen <hansen@kde.org>
00005  *
00006  *      Ported to KABC by Daniel Molkentin <molkentin@kde.org>
00007  *
00008  * This file is free software; you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation; either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * This file is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
00021  */
00022 
00023 
00024 
00025 #include <qfile.h>
00026 #include <qimage.h>
00027 #include <qlabel.h>
00028 #include <qpixmap.h>
00029 #include <qtextstream.h>
00030 #include <qurl.h>
00031 
00032 #include <kapplication.h>
00033 #include <kconfig.h>
00034 #include <kdebug.h>
00035 #include <kdirwatch.h>
00036 #include <kmdcodec.h>
00037 #include <kprotocolinfo.h>
00038 #include <kstandarddirs.h>
00039 
00040 #include "ldapclient.h"
00041 #include "ldif.h"
00042 #include "ldapurl.h"
00043 
00044 using namespace KPIM;
00045 
00046 class LdapClient::LdapClientPrivate{
00047 public:
00048   QString bindDN;
00049   QString pwdBindDN;
00050   LDIF ldif;
00051   int clientNumber;
00052   int completionWeight;
00053 };
00054 
00055 QString LdapObject::toString() const
00056 {
00057   QString result = QString::fromLatin1( "\ndn: %1\n" ).arg( dn );
00058   for ( LdapAttrMap::ConstIterator it = attrs.begin(); it != attrs.end(); ++it ) {
00059     QString attr = it.key();
00060     for ( LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2 ) {
00061       result += QString::fromUtf8( LDIF::assembleLine( attr, *it2, 76 ) ) + "\n";
00062     }
00063   }
00064 
00065   return result;
00066 }
00067 
00068 void LdapObject::clear()
00069 {
00070   dn = QString::null;
00071   objectClass = QString::null;
00072   attrs.clear();
00073 }
00074 
00075 void LdapObject::assign( const LdapObject& that )
00076 {
00077   if ( &that != this ) {
00078     dn = that.dn;
00079     attrs = that.attrs;
00080     client = that.client;
00081   }
00082 }
00083 
00084 LdapClient::LdapClient( int clientNumber, QObject* parent, const char* name )
00085   : QObject( parent, name ), mJob( 0 ), mActive( false ), mReportObjectClass( false )
00086 {
00087   d = new LdapClientPrivate;
00088   d->clientNumber = clientNumber;
00089   d->completionWeight = 50 - clientNumber;
00090 }
00091 
00092 LdapClient::~LdapClient()
00093 {
00094   cancelQuery();
00095   delete d; d = 0;
00096 }
00097 
00098 void LdapClient::setHost( const QString& host )
00099 {
00100   mHost = host;
00101 }
00102 
00103 void LdapClient::setPort( const QString& port )
00104 {
00105   mPort = port;
00106 }
00107 
00108 void LdapClient::setBase( const QString& base )
00109 {
00110   mBase = base;
00111 }
00112 
00113 void LdapClient::setBindDN( const QString& bindDN )
00114 {
00115   d->bindDN = bindDN;
00116 }
00117 
00118 void LdapClient::setPwdBindDN( const QString& pwdBindDN )
00119 {
00120   d->pwdBindDN = pwdBindDN;
00121 }
00122 
00123 void LdapClient::setAttrs( const QStringList& attrs )
00124 {
00125   mAttrs = attrs;
00126   for ( QStringList::Iterator it = mAttrs.begin(); it != mAttrs.end(); ++it )
00127     if( (*it).lower() == "objectclass" ){
00128       mReportObjectClass = true;
00129       return;
00130     }
00131   mAttrs << "objectClass"; // via objectClass we detect distribution lists
00132   mReportObjectClass = false;
00133 }
00134 
00135 void LdapClient::startQuery( const QString& filter )
00136 {
00137   cancelQuery();
00138   LDAPUrl url;
00139 
00140   url.setProtocol( "ldap" );
00141   url.setUser( d->bindDN );
00142   url.setPass( d->pwdBindDN );
00143   url.setHost( mHost );
00144   url.setPort( mPort.toUInt() );
00145   url.setDn( mBase );
00146   url.setAttributes( mAttrs );
00147   url.setScope( mScope == "one" ? LDAPUrl::One : LDAPUrl::Sub );
00148   url.setFilter( "("+filter+")" );
00149 
00150   kdDebug(5300) << "LdapClient: Doing query: " << url.prettyURL() << endl;
00151 
00152   startParseLDIF();
00153   mActive = true;
00154   mJob = KIO::get( url, false, false );
00155   connect( mJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
00156            this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
00157   connect( mJob, SIGNAL( infoMessage( KIO::Job*, const QString& ) ),
00158            this, SLOT( slotInfoMessage( KIO::Job*, const QString& ) ) );
00159   connect( mJob, SIGNAL( result( KIO::Job* ) ),
00160            this, SLOT( slotDone() ) );
00161 }
00162 
00163 void LdapClient::cancelQuery()
00164 {
00165   if ( mJob ) {
00166     mJob->kill();
00167     mJob = 0;
00168   }
00169 
00170   mActive = false;
00171 }
00172 
00173 void LdapClient::slotData( KIO::Job*, const QByteArray& data )
00174 {
00175   parseLDIF( data );
00176 }
00177 
00178 void LdapClient::slotInfoMessage( KIO::Job*, const QString & )
00179 {
00180   //qDebug("Job said \"%s\"", info.latin1());
00181 }
00182 
00183 void LdapClient::slotDone()
00184 {
00185   endParseLDIF();
00186   mActive = false;
00187 #if 0
00188   for ( QValueList<LdapObject>::Iterator it = mObjects.begin(); it != mObjects.end(); ++it ) {
00189     qDebug( (*it).toString().latin1() );
00190   }
00191 #endif
00192   int err = mJob->error();
00193   if ( err && err != KIO::ERR_USER_CANCELED ) {
00194     emit error( KIO::buildErrorString( err, QString("%1:%2").arg( mHost ).arg( mPort ) ) );
00195   }
00196   emit done();
00197 }
00198 
00199 void LdapClient::startParseLDIF()
00200 {
00201   mCurrentObject.clear();
00202   mLastAttrName  = 0;
00203   mLastAttrValue = 0;
00204   mIsBase64 = false;
00205   d->ldif.startParsing();
00206 }
00207 
00208 void LdapClient::endParseLDIF()
00209 {
00210 }
00211 
00212 void LdapClient::finishCurrentObject()
00213 {
00214   mCurrentObject.dn = d->ldif.dn();
00215   const QString sClass( mCurrentObject.objectClass.lower() );
00216   if( sClass == "groupofnames" || sClass == "kolabgroupofnames" ){
00217     LdapAttrMap::ConstIterator it = mCurrentObject.attrs.find("mail");
00218     if( it == mCurrentObject.attrs.end() ){
00219       // No explicit mail address found so far?
00220       // Fine, then we use the address stored in the DN.
00221       QString sMail;
00222       QStringList lMail = QStringList::split(",dc=", mCurrentObject.dn);
00223       const int n = lMail.count();
00224       if( n ){
00225         if( lMail.first().lower().startsWith("cn=") ){
00226           sMail = lMail.first().simplifyWhiteSpace().mid(3);
00227           if( 1 < n )
00228             sMail.append('@');
00229           for( int i=1; i<n; ++i){
00230             sMail.append( lMail[i] );
00231             if( i < n-1 )
00232               sMail.append('.');
00233           }
00234           mCurrentObject.attrs["mail"].append( sMail.utf8() );
00235         }
00236       }
00237     }
00238   }
00239   mCurrentObject.client = this;
00240   emit result( mCurrentObject );
00241   mCurrentObject.clear();
00242 }
00243 
00244 void LdapClient::parseLDIF( const QByteArray& data )
00245 {
00246   //kdDebug(5300) << "LdapClient::parseLDIF( " << QCString(data.data(), data.size()+1) << " )" << endl;
00247   if ( data.size() ) {
00248     d->ldif.setLDIF( data );
00249   } else {
00250     d->ldif.endLDIF();
00251   }
00252 
00253   LDIF::ParseVal ret;
00254   QString name;
00255   do {
00256     ret = d->ldif.nextItem();
00257     switch ( ret ) {
00258       case LDIF::Item:
00259         {
00260           name = d->ldif.attr();
00261           // Must make a copy! QByteArray is explicitely shared
00262           QByteArray value = d->ldif.val().copy();
00263           bool bIsObjectClass = name.lower() == "objectclass";
00264           if( bIsObjectClass )
00265             mCurrentObject.objectClass = QString::fromUtf8( value, value.size() );
00266           if( mReportObjectClass || !bIsObjectClass )
00267             mCurrentObject.attrs[ name ].append( value );
00268           //kdDebug(5300) << "LdapClient::parseLDIF(): name=" << name << " value=" << QCString(value.data(), value.size()+1) << endl;
00269         }
00270         break;
00271      case LDIF::EndEntry:
00272         finishCurrentObject();
00273         break;
00274       default:
00275         break;
00276     }
00277   } while ( ret != LDIF::MoreData );
00278 }
00279 
00280 QString LdapClient::bindDN() const
00281 {
00282   return d->bindDN;
00283 }
00284 
00285 QString LdapClient::pwdBindDN() const
00286 {
00287   return d->pwdBindDN;
00288 }
00289 
00290 int LdapClient::clientNumber() const
00291 {
00292   return d->clientNumber;
00293 }
00294 
00295 int LdapClient::completionWeight() const
00296 {
00297   return d->completionWeight;
00298 }
00299 
00300 void KPIM::LdapClient::setCompletionWeight( int weight )
00301 {
00302   d->completionWeight = weight;
00303 }
00304 
00305 LdapSearch::LdapSearch()
00306     : mActiveClients( 0 ), mNoLDAPLookup( false )
00307 {
00308   if ( !KProtocolInfo::isKnownProtocol( KURL("ldap://localhost") ) ) {
00309     mNoLDAPLookup = true;
00310     return;
00311   }
00312 
00313   readConfig();
00314   connect(KDirWatch::self(), SIGNAL(dirty (const QString&)),this,
00315           SLOT(slotFileChanged(const QString&)));
00316 }
00317 
00318 void LdapSearch::readConfig()
00319 {
00320   //kdDebug(5300) << "LdapClient::readConfig()" << endl;
00321   cancelSearch();
00322   QValueList< LdapClient* >::Iterator it;
00323   for ( it = mClients.begin(); it != mClients.end(); ++it )
00324     delete *it;
00325   mClients.clear();
00326 
00327   // stolen from KAddressBook
00328   KConfig config( "kabldaprc", true );
00329   config.setGroup( "LDAP" );
00330   int numHosts = config.readUnsignedNumEntry( "NumSelectedHosts");
00331   if ( !numHosts ) {
00332     mNoLDAPLookup = true;
00333   } else {
00334     for ( int j = 0; j < numHosts; j++ ) {
00335       LdapClient* ldapClient = new LdapClient( j, this );
00336 
00337       QString host =  config.readEntry( QString( "SelectedHost%1" ).arg( j ), "" ).stripWhiteSpace();
00338       if ( !host.isEmpty() ){
00339         ldapClient->setHost( host );
00340         mNoLDAPLookup = false;
00341       }
00342 
00343       QString port = QString::number( config.readUnsignedNumEntry( QString( "SelectedPort%1" ).arg( j ) ) );
00344       if ( !port.isEmpty() )
00345         ldapClient->setPort( port );
00346 
00347       QString base = config.readEntry( QString( "SelectedBase%1" ).arg( j ), "" ).stripWhiteSpace();
00348       if ( !base.isEmpty() )
00349         ldapClient->setBase( base );
00350 
00351       QString bindDN = config.readEntry( QString( "SelectedBind%1" ).arg( j ) ).stripWhiteSpace();
00352       if ( !bindDN.isEmpty() )
00353         ldapClient->setBindDN( bindDN );
00354 
00355       QString pwdBindDN = config.readEntry( QString( "SelectedPwdBind%1" ).arg( j ) ).stripWhiteSpace();
00356       if ( !pwdBindDN.isEmpty() )
00357         ldapClient->setPwdBindDN( pwdBindDN );
00358 
00359       int completionWeight = config.readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( j ), -1 );
00360       if ( completionWeight != -1 )
00361         ldapClient->setCompletionWeight( completionWeight );
00362 
00363       QStringList attrs;
00364       // note: we need "objectClass" to detect distribution lists
00365       attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass";
00366       ldapClient->setAttrs( attrs );
00367 
00368       connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ),
00369                this, SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) );
00370       connect( ldapClient, SIGNAL( done() ),
00371                this, SLOT( slotLDAPDone() ) );
00372       connect( ldapClient, SIGNAL( error( const QString& ) ),
00373                this, SLOT( slotLDAPError( const QString& ) ) );
00374 
00375       mClients.append( ldapClient );
00376     }
00377 
00378     connect( &mDataTimer, SIGNAL( timeout() ), SLOT( slotDataTimer() ) );
00379   }
00380   mConfigFile = locateLocal( "config", "kabldaprc" );
00381   KDirWatch::self()->addFile( mConfigFile );
00382 }
00383 
00384 void LdapSearch::slotFileChanged( const QString& file )
00385 {
00386   //kdDebug(5300) << "LdapClient::slotFileChanged( " << file << " )" << endl;
00387   if ( file == mConfigFile )
00388     readConfig();
00389 }
00390 
00391 void LdapSearch::startSearch( const QString& txt )
00392 {
00393   if ( mNoLDAPLookup )
00394     return;
00395 
00396   cancelSearch();
00397 
00398   int pos = txt.find( '\"' );
00399   if( pos >= 0 )
00400   {
00401     ++pos;
00402     int pos2 = txt.find( '\"', pos );
00403     if( pos2 >= 0 )
00404         mSearchText = txt.mid( pos , pos2 - pos );
00405     else
00406         mSearchText = txt.mid( pos );
00407   } else
00408     mSearchText = txt;
00409 
00410   /* The reasoning behind this filter is:
00411    * If it's a person, or a distlist, show it, even if it doesn't have an email address.
00412    * If it's not a person, or a distlist, only show it if it has an email attribute.
00413    * This allows both resource accounts with an email address which are not a person and
00414    * person entries without an email address to show up, while still not showing things
00415    * like structural entries in the ldap tree. */
00416   QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(givenName=%3*)(sn=%4*))" )
00417       .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText );
00418 
00419   QValueList< LdapClient* >::Iterator it;
00420   for ( it = mClients.begin(); it != mClients.end(); ++it ) {
00421     (*it)->startQuery( filter );
00422     kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl;
00423     ++mActiveClients;
00424   }
00425 }
00426 
00427 void LdapSearch::cancelSearch()
00428 {
00429   QValueList< LdapClient* >::Iterator it;
00430   for ( it = mClients.begin(); it != mClients.end(); ++it )
00431     (*it)->cancelQuery();
00432 
00433   mActiveClients = 0;
00434   mResults.clear();
00435 }
00436 
00437 void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj )
00438 {
00439   mResults.append( obj );
00440   if ( !mDataTimer.isActive() )
00441     mDataTimer.start( 500, true );
00442 }
00443 
00444 void LdapSearch::slotLDAPError( const QString& )
00445 {
00446   slotLDAPDone();
00447 }
00448 
00449 void LdapSearch::slotLDAPDone()
00450 {
00451   if ( --mActiveClients > 0 )
00452     return;
00453 
00454   finish();
00455 }
00456 
00457 void LdapSearch::slotDataTimer()
00458 {
00459   QStringList lst;
00460   LdapResultList reslist;
00461   makeSearchData( lst, reslist );
00462   if ( !lst.isEmpty() )
00463     emit searchData( lst );
00464   if ( !reslist.isEmpty() )
00465     emit searchData( reslist );
00466 }
00467 
00468 void LdapSearch::finish()
00469 {
00470   mDataTimer.stop();
00471 
00472   slotDataTimer(); // emit final bunch of data
00473   emit searchDone();
00474 }
00475 
00476 void LdapSearch::makeSearchData( QStringList& ret, LdapResultList& resList )
00477 {
00478   QString search_text_upper = mSearchText.upper();
00479 
00480   QValueList< KPIM::LdapObject >::ConstIterator it1;
00481   for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) {
00482     QString name, mail, givenname, sn;
00483     QStringList mails;
00484     bool isDistributionList = false;
00485     bool wasCN = false;
00486     bool wasDC = false;
00487 
00488     kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl;
00489 
00490     LdapAttrMap::ConstIterator it2;
00491     for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) {
00492       QByteArray val = (*it2).first();
00493       int len = val.size();
00494       if( len > 0 && '\0' == val[len-1] )
00495         --len;
00496       const QString tmp = QString::fromUtf8( val, len );
00497       kdDebug(5300) << "      key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl;
00498       if ( it2.key() == "cn" ) {
00499         name = tmp;
00500         if( mail.isEmpty() )
00501           mail = tmp;
00502         else{
00503           if( wasCN )
00504             mail.prepend( "." );
00505           else
00506             mail.prepend( "@" );
00507           mail.prepend( tmp );
00508         }
00509         wasCN = true;
00510       } else if ( it2.key() == "dc" ) {
00511         if( mail.isEmpty() )
00512           mail = tmp;
00513         else{
00514           if( wasDC )
00515             mail.append( "." );
00516           else
00517             mail.append( "@" );
00518           mail.append( tmp );
00519         }
00520         wasDC = true;
00521       } else if( it2.key() == "mail" ) {
00522         mail = tmp;
00523         LdapAttrValue::ConstIterator it3 = it2.data().begin();
00524         for ( ; it3 != it2.data().end(); ++it3 ) {
00525           mails.append( QString::fromUtf8( (*it3).data(), (*it3).size() ) );
00526         }
00527       } else if( it2.key() == "givenName" )
00528         givenname = tmp;
00529       else if( it2.key() == "sn" )
00530         sn = tmp;
00531       else if( it2.key() == "objectClass" &&
00532                ( tmp == "groupOfNames" || tmp == "kolabGroupOfNames" ) ) {
00533         isDistributionList = true;
00534       }
00535     }
00536 
00537     if( mails.isEmpty()) {
00538       if ( !mail.isEmpty() ) mails.append( mail );
00539       if( isDistributionList ) {
00540         kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl;
00541         ret.append( name );
00542         // following lines commented out for bugfixing kolab issue #177:
00543         //
00544         // Unlike we thought previously we may NOT append the server name here.
00545         //
00546         // The right server is found by the SMTP server instead: Kolab users
00547         // must use the correct SMTP server, by definition.
00548         //
00549         //mail = (*it1).client->base().simplifyWhiteSpace();
00550         //mail.replace( ",dc=", ".", false );
00551         //if( mail.startsWith("dc=", false) )
00552         //  mail.remove(0, 3);
00553         //mail.prepend( '@' );
00554         //mail.prepend( name );
00555         //mail = name;
00556       } else {
00557         kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl;
00558         continue; // nothing, bad entry
00559       }
00560     } else if ( name.isEmpty() ) {
00561       kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl;
00562       ret.append( mail );
00563     } else {
00564       kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\"  mail: \"" << mail << "\"" << endl;
00565       ret.append( QString( "%1 <%2>" ).arg( name ).arg( mail ) );
00566     }
00567 
00568     LdapResult sr;
00569     sr.clientNumber = (*it1).client->clientNumber();
00570     sr.completionWeight = (*it1).client->completionWeight();
00571     sr.name = name;
00572     sr.email = mails;
00573     resList.append( sr );
00574   }
00575 
00576   mResults.clear();
00577 }
00578 
00579 bool LdapSearch::isAvailable() const
00580 {
00581   return !mNoLDAPLookup;
00582 }
00583 
00584 #include "ldapclient.moc"
KDE Logo
This file is part of the documentation for libkdepim Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 4 14:40:45 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003