libkdepim

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <kabc/ldapurl.h>
00033 #include <kabc/ldif.h>
00034 #include <kapplication.h>
00035 #include <kconfig.h>
00036 #include <kdebug.h>
00037 #include <kdirwatch.h>
00038 #include <kmdcodec.h>
00039 #include <kprotocolinfo.h>
00040 #include <kstandarddirs.h>
00041 #include <kstaticdeleter.h>
00042 
00043 #include "ldapclient.h"
00044 
00045 using namespace KPIM;
00046 
00047 KConfig *KPIM::LdapSearch::s_config = 0L;
00048 static KStaticDeleter<KConfig> configDeleter;
00049 
00050 QString LdapObject::toString() const
00051 {
00052   QString result = QString::fromLatin1( "\ndn: %1\n" ).arg( dn );
00053   for ( LdapAttrMap::ConstIterator it = attrs.begin(); it != attrs.end(); ++it ) {
00054     QString attr = it.key();
00055     for ( LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2 ) {
00056       result += QString::fromUtf8( KABC::LDIF::assembleLine( attr, *it2, 76 ) ) + "\n";
00057     }
00058   }
00059 
00060   return result;
00061 }
00062 
00063 void LdapObject::clear()
00064 {
00065   dn = QString::null;
00066   objectClass = QString::null;
00067   attrs.clear();
00068 }
00069 
00070 void LdapObject::assign( const LdapObject& that )
00071 {
00072   if ( &that != this ) {
00073     dn = that.dn;
00074     attrs = that.attrs;
00075     client = that.client;
00076   }
00077 }
00078 
00079 LdapClient::LdapClient( int clientNumber, QObject* parent, const char* name )
00080   : QObject( parent, name ), mJob( 0 ), mActive( false ), mReportObjectClass( false )
00081 {
00082 //  d = new LdapClientPrivate;
00083   mClientNumber = clientNumber;
00084   mCompletionWeight = 50 - mClientNumber;
00085 }
00086 
00087 LdapClient::~LdapClient()
00088 {
00089   cancelQuery();
00090 //  delete d; d = 0;
00091 }
00092 
00093 void LdapClient::setAttrs( const QStringList& attrs )
00094 {
00095   mAttrs = attrs;
00096   for ( QStringList::Iterator it = mAttrs.begin(); it != mAttrs.end(); ++it )
00097     if( (*it).lower() == "objectclass" ){
00098       mReportObjectClass = true;
00099       return;
00100     }
00101   mAttrs << "objectClass"; // via objectClass we detect distribution lists
00102   mReportObjectClass = false;
00103 }
00104 
00105 void LdapClient::startQuery( const QString& filter )
00106 {
00107   cancelQuery();
00108   KABC::LDAPUrl url;
00109 
00110   url.setProtocol( ( mServer.security() == LdapServer::SSL ) ? "ldaps" : "ldap" );
00111   if ( mServer.auth() != LdapServer::Anonymous ) {
00112     url.setUser( mServer.user() );
00113     url.setPass( mServer.pwdBindDN() );
00114   }
00115   url.setHost( mServer.host() );
00116   url.setPort( mServer.port() );
00117   url.setExtension( "x-ver", QString::number( mServer.version() ) );
00118   url.setDn( mServer.baseDN() );
00119   url.setDn( mServer.baseDN() );
00120   if ( mServer.security() == LdapServer::TLS ) url.setExtension( "x-tls","" );
00121   if ( mServer.auth() == LdapServer::SASL ) {
00122     url.setExtension( "x-sasl","" );
00123     if ( !mServer.bindDN().isEmpty() ) url.setExtension( "x-bindname", mServer.bindDN() );
00124     if ( !mServer.mech().isEmpty() ) url.setExtension( "x-mech", mServer.mech() );
00125   }
00126   if ( mServer.timeLimit() != 0 ) url.setExtension( "x-timelimit", 
00127     QString::number( mServer.timeLimit() ) );
00128   if ( mServer.sizeLimit() != 0 ) url.setExtension( "x-sizelimit", 
00129     QString::number( mServer.sizeLimit() ) );
00130   
00131   url.setAttributes( mAttrs );
00132   url.setScope( mScope == "one" ? KABC::LDAPUrl::One : KABC::LDAPUrl::Sub );
00133   url.setFilter( "("+filter+")" );
00134 
00135   kdDebug(5300) << "LdapClient: Doing query: " << url.prettyURL() << endl;
00136 
00137   startParseLDIF();
00138   mActive = true;
00139   mJob = KIO::get( url, false, false );
00140   connect( mJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
00141            this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
00142   connect( mJob, SIGNAL( infoMessage( KIO::Job*, const QString& ) ),
00143            this, SLOT( slotInfoMessage( KIO::Job*, const QString& ) ) );
00144   connect( mJob, SIGNAL( result( KIO::Job* ) ),
00145            this, SLOT( slotDone() ) );
00146 }
00147 
00148 void LdapClient::cancelQuery()
00149 {
00150   if ( mJob ) {
00151     mJob->kill();
00152     mJob = 0;
00153   }
00154 
00155   mActive = false;
00156 }
00157 
00158 void LdapClient::slotData( KIO::Job*, const QByteArray& data )
00159 {
00160   parseLDIF( data );
00161 }
00162 
00163 void LdapClient::slotInfoMessage( KIO::Job*, const QString & )
00164 {
00165   //qDebug("Job said \"%s\"", info.latin1());
00166 }
00167 
00168 void LdapClient::slotDone()
00169 {
00170   endParseLDIF();
00171   mActive = false;
00172 #if 0
00173   for ( QValueList<LdapObject>::Iterator it = mObjects.begin(); it != mObjects.end(); ++it ) {
00174     qDebug( (*it).toString().latin1() );
00175   }
00176 #endif
00177   int err = mJob->error();
00178   if ( err && err != KIO::ERR_USER_CANCELED ) {
00179     emit error( mJob->errorString() );
00180   }
00181   emit done();
00182 }
00183 
00184 void LdapClient::startParseLDIF()
00185 {
00186   mCurrentObject.clear();
00187   mLdif.startParsing();
00188 }
00189 
00190 void LdapClient::endParseLDIF()
00191 {
00192 }
00193 
00194 void LdapClient::finishCurrentObject()
00195 {
00196   mCurrentObject.dn = mLdif.dn();
00197   const QString sClass( mCurrentObject.objectClass.lower() );
00198   if( sClass == "groupofnames" || sClass == "kolabgroupofnames" ){
00199     LdapAttrMap::ConstIterator it = mCurrentObject.attrs.find("mail");
00200     if( it == mCurrentObject.attrs.end() ){
00201       // No explicit mail address found so far?
00202       // Fine, then we use the address stored in the DN.
00203       QString sMail;
00204       QStringList lMail = QStringList::split(",dc=", mCurrentObject.dn);
00205       const int n = lMail.count();
00206       if( n ){
00207         if( lMail.first().lower().startsWith("cn=") ){
00208           sMail = lMail.first().simplifyWhiteSpace().mid(3);
00209           if( 1 < n )
00210             sMail.append('@');
00211           for( int i=1; i<n; ++i){
00212             sMail.append( lMail[i] );
00213             if( i < n-1 )
00214               sMail.append('.');
00215           }
00216           mCurrentObject.attrs["mail"].append( sMail.utf8() );
00217         }
00218       }
00219     }
00220   }
00221   mCurrentObject.client = this;
00222   emit result( mCurrentObject );
00223   mCurrentObject.clear();
00224 }
00225 
00226 void LdapClient::parseLDIF( const QByteArray& data )
00227 {
00228   //kdDebug(5300) << "LdapClient::parseLDIF( " << QCString(data.data(), data.size()+1) << " )" << endl;
00229   if ( data.size() ) {
00230     mLdif.setLDIF( data );
00231   } else {
00232     mLdif.endLDIF();
00233   }
00234 
00235   KABC::LDIF::ParseVal ret;
00236   QString name;
00237   do {
00238     ret = mLdif.nextItem();
00239     switch ( ret ) {
00240       case KABC::LDIF::Item: 
00241         {
00242           name = mLdif.attr();
00243           // Must make a copy! QByteArray is explicitely shared
00244           QByteArray value = mLdif.val().copy();
00245           bool bIsObjectClass = name.lower() == "objectclass";
00246           if( bIsObjectClass )
00247             mCurrentObject.objectClass = QString::fromUtf8( value, value.size() );
00248           if( mReportObjectClass || !bIsObjectClass )
00249             mCurrentObject.attrs[ name ].append( value );
00250           //kdDebug(5300) << "LdapClient::parseLDIF(): name=" << name << " value=" << QCString(value.data(), value.size()+1) << endl;
00251         }
00252         break;
00253      case KABC::LDIF::EndEntry:
00254         finishCurrentObject();
00255         break;
00256       default:
00257         break;
00258     }
00259   } while ( ret != KABC::LDIF::MoreData );
00260 }
00261 
00262 int LdapClient::clientNumber() const
00263 {
00264   return mClientNumber;
00265 }
00266 
00267 int LdapClient::completionWeight() const
00268 {
00269   return mCompletionWeight;
00270 }
00271 
00272 void LdapClient::setCompletionWeight( int weight )
00273 {
00274   mCompletionWeight = weight;
00275 }
00276 
00277 void LdapSearch::readConfig( LdapServer &server, KConfig *config, int j, bool active )
00278 {
00279   QString prefix;
00280   if ( active ) prefix = "Selected";
00281   QString host =  config->readEntry( prefix + QString( "Host%1" ).arg( j ), "" ).stripWhiteSpace();
00282   if ( !host.isEmpty() )
00283     server.setHost( host );
00284 
00285   int port = config->readNumEntry( prefix + QString( "Port%1" ).arg( j ), 389 );
00286   server.setPort( port );
00287 
00288   QString base = config->readEntry( prefix + QString( "Base%1" ).arg( j ), "" ).stripWhiteSpace();
00289   if ( !base.isEmpty() )
00290     server.setBaseDN( base );
00291 
00292   QString user = config->readEntry( prefix + QString( "User%1" ).arg( j ) ).stripWhiteSpace();
00293   if ( !user.isEmpty() )
00294     server.setUser( user );
00295 
00296   QString bindDN = config->readEntry( prefix + QString( "Bind%1" ).arg( j ) ).stripWhiteSpace();
00297   if ( !bindDN.isEmpty() )
00298     server.setBindDN( bindDN );
00299 
00300   QString pwdBindDN = config->readEntry( prefix + QString( "PwdBind%1" ).arg( j ) );
00301   if ( !pwdBindDN.isEmpty() )
00302     server.setPwdBindDN( pwdBindDN );
00303 
00304   server.setTimeLimit( config->readNumEntry( prefix + QString( "TimeLimit%1" ).arg( j ) ) );
00305   server.setSizeLimit( config->readNumEntry( prefix + QString( "SizeLimit%1" ).arg( j ) ) );
00306   server.setVersion( config->readNumEntry( prefix + QString( "Version%1" ).arg( j ), 3 ) );
00307   server.setSecurity( config->readNumEntry( prefix + QString( "Security%1" ).arg( j ) ) );
00308   server.setAuth( config->readNumEntry( prefix + QString( "Auth%1" ).arg( j ) ) );
00309   server.setMech( config->readEntry( prefix + QString( "Mech%1" ).arg( j ) ) );
00310 }
00311 
00312 void LdapSearch::writeConfig( const LdapServer &server, KConfig *config, int j, bool active )
00313 {
00314   QString prefix;
00315   if ( active ) prefix = "Selected";
00316   config->writeEntry( prefix + QString( "Host%1" ).arg( j ), server.host() );
00317   config->writeEntry( prefix + QString( "Port%1" ).arg( j ), server.port() );
00318   config->writeEntry( prefix + QString( "Base%1" ).arg( j ), server.baseDN() );
00319   config->writeEntry( prefix + QString( "User%1" ).arg( j ), server.user() );
00320   config->writeEntry( prefix + QString( "Bind%1" ).arg( j ), server.bindDN() );
00321   config->writeEntry( prefix + QString( "PwdBind%1" ).arg( j ), server.pwdBindDN() );
00322   config->writeEntry( prefix + QString( "TimeLimit%1" ).arg( j ), server.timeLimit() );
00323   config->writeEntry( prefix + QString( "SizeLimit%1" ).arg( j ), server.sizeLimit() );
00324   config->writeEntry( prefix + QString( "Version%1" ).arg( j ), server.version() );
00325   config->writeEntry( prefix + QString( "Security%1" ).arg( j ), server.security() );
00326   config->writeEntry( prefix + QString( "Auth%1" ).arg( j ), server.auth() );
00327   config->writeEntry( prefix + QString( "Mech%1" ).arg( j ), server.mech() );
00328 }
00329 
00330 KConfig* LdapSearch::config()
00331 {
00332   if ( !s_config )
00333     configDeleter.setObject( s_config, new KConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals
00334 
00335   return s_config;
00336 }
00337 
00338 
00339 LdapSearch::LdapSearch()
00340     : mActiveClients( 0 ), mNoLDAPLookup( false )
00341 {
00342   if ( !KProtocolInfo::isKnownProtocol( KURL("ldap://localhost") ) ) {
00343     mNoLDAPLookup = true;
00344     return;
00345   }
00346 
00347   readConfig();
00348   connect(KDirWatch::self(), SIGNAL(dirty (const QString&)),this,
00349           SLOT(slotFileChanged(const QString&)));
00350 }
00351 
00352 void LdapSearch::readConfig()
00353 {
00354   cancelSearch();
00355   QValueList< LdapClient* >::Iterator it;
00356   for ( it = mClients.begin(); it != mClients.end(); ++it )
00357     delete *it;
00358   mClients.clear();
00359 
00360   // stolen from KAddressBook
00361   KConfig *config = KPIM::LdapSearch::config();
00362   config->setGroup( "LDAP" );
00363   int numHosts = config->readUnsignedNumEntry( "NumSelectedHosts");
00364   if ( !numHosts ) {
00365     mNoLDAPLookup = true;
00366   } else {
00367     for ( int j = 0; j < numHosts; j++ ) {
00368       LdapClient* ldapClient = new LdapClient( j, this );
00369       LdapServer server;
00370       readConfig( server, config, j, true );
00371       if ( !server.host().isEmpty() ) mNoLDAPLookup = false;
00372       ldapClient->setServer( server );
00373 
00374       int completionWeight = config->readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( j ), -1 );
00375       if ( completionWeight != -1 )
00376         ldapClient->setCompletionWeight( completionWeight );
00377 
00378       QStringList attrs;
00379       // note: we need "objectClass" to detect distribution lists
00380       attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass";
00381       ldapClient->setAttrs( attrs );
00382 
00383       connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ),
00384                this, SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) );
00385       connect( ldapClient, SIGNAL( done() ),
00386                this, SLOT( slotLDAPDone() ) );
00387       connect( ldapClient, SIGNAL( error( const QString& ) ),
00388                this, SLOT( slotLDAPError( const QString& ) ) );
00389 
00390       mClients.append( ldapClient );
00391     }
00392 
00393     connect( &mDataTimer, SIGNAL( timeout() ), SLOT( slotDataTimer() ) );
00394   }
00395   mConfigFile = locateLocal( "config", "kabldaprc" );
00396   KDirWatch::self()->addFile( mConfigFile );
00397 }
00398 
00399 void LdapSearch::slotFileChanged( const QString& file )
00400 {
00401   if ( file == mConfigFile )
00402     readConfig();
00403 }
00404 
00405 void LdapSearch::startSearch( const QString& txt )
00406 {
00407   if ( mNoLDAPLookup )
00408     return;
00409 
00410   cancelSearch();
00411 
00412   int pos = txt.find( '\"' );
00413   if( pos >= 0 )
00414   {
00415     ++pos;
00416     int pos2 = txt.find( '\"', pos );
00417     if( pos2 >= 0 )
00418         mSearchText = txt.mid( pos , pos2 - pos );
00419     else
00420         mSearchText = txt.mid( pos );
00421   } else
00422     mSearchText = txt;
00423 
00424   /* The reasoning behind this filter is:
00425    * If it's a person, or a distlist, show it, even if it doesn't have an email address.
00426    * If it's not a person, or a distlist, only show it if it has an email attribute.
00427    * This allows both resource accounts with an email address which are not a person and
00428    * person entries without an email address to show up, while still not showing things
00429    * like structural entries in the ldap tree. */
00430   QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(givenName=%3*)(sn=%4*))" )
00431       .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText );
00432 
00433   QValueList< LdapClient* >::Iterator it;
00434   for ( it = mClients.begin(); it != mClients.end(); ++it ) {
00435     (*it)->startQuery( filter );
00436     kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl;
00437     ++mActiveClients;
00438   }
00439 }
00440 
00441 void LdapSearch::cancelSearch()
00442 {
00443   QValueList< LdapClient* >::Iterator it;
00444   for ( it = mClients.begin(); it != mClients.end(); ++it )
00445     (*it)->cancelQuery();
00446 
00447   mActiveClients = 0;
00448   mResults.clear();
00449 }
00450 
00451 void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj )
00452 {
00453   mResults.append( obj );
00454   if ( !mDataTimer.isActive() )
00455     mDataTimer.start( 500, true );
00456 }
00457 
00458 void LdapSearch::slotLDAPError( const QString& )
00459 {
00460   slotLDAPDone();
00461 }
00462 
00463 void LdapSearch::slotLDAPDone()
00464 {
00465   if ( --mActiveClients > 0 )
00466     return;
00467 
00468   finish();
00469 }
00470 
00471 void LdapSearch::slotDataTimer()
00472 {
00473   QStringList lst;
00474   LdapResultList reslist;
00475   makeSearchData( lst, reslist );
00476   if ( !lst.isEmpty() )
00477     emit searchData( lst );
00478   if ( !reslist.isEmpty() )
00479     emit searchData( reslist );
00480 }
00481 
00482 void LdapSearch::finish()
00483 {
00484   mDataTimer.stop();
00485 
00486   slotDataTimer(); // emit final bunch of data
00487   emit searchDone();
00488 }
00489 
00490 void LdapSearch::makeSearchData( QStringList& ret, LdapResultList& resList )
00491 {
00492   QString search_text_upper = mSearchText.upper();
00493 
00494   QValueList< KPIM::LdapObject >::ConstIterator it1;
00495   for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) {
00496     QString name, mail, givenname, sn;
00497     QStringList mails;
00498     bool isDistributionList = false;
00499     bool wasCN = false;
00500     bool wasDC = false;
00501 
00502     kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl;
00503 
00504     LdapAttrMap::ConstIterator it2;
00505     for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) {
00506       QByteArray val = (*it2).first();
00507       int len = val.size();
00508       if( len > 0 && '\0' == val[len-1] )
00509         --len;
00510       const QString tmp = QString::fromUtf8( val, len );
00511       kdDebug(5300) << "      key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl;
00512       if ( it2.key() == "cn" ) {
00513         name = tmp;
00514         if( mail.isEmpty() )
00515           mail = tmp;
00516         else{
00517           if( wasCN )
00518             mail.prepend( "." );
00519           else
00520             mail.prepend( "@" );
00521           mail.prepend( tmp );
00522         }
00523         wasCN = true;
00524       } else if ( it2.key() == "dc" ) {
00525         if( mail.isEmpty() )
00526           mail = tmp;
00527         else{
00528           if( wasDC )
00529             mail.append( "." );
00530           else
00531             mail.append( "@" );
00532           mail.append( tmp );
00533         }
00534         wasDC = true;
00535       } else if( it2.key() == "mail" ) {
00536         mail = tmp;
00537         LdapAttrValue::ConstIterator it3 = it2.data().begin();
00538         for ( ; it3 != it2.data().end(); ++it3 ) {
00539           mails.append( QString::fromUtf8( (*it3).data(), (*it3).size() ) );
00540         }
00541       } else if( it2.key() == "givenName" )
00542         givenname = tmp;
00543       else if( it2.key() == "sn" )
00544         sn = tmp;
00545       else if( it2.key() == "objectClass" &&
00546                (tmp == "groupOfNames" || tmp == "kolabGroupOfNames") ) {
00547         isDistributionList = true;
00548       }
00549     }
00550 
00551     if( mails.isEmpty()) {
00552       if ( !mail.isEmpty() ) mails.append( mail );
00553       if( isDistributionList ) {
00554         kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl;
00555         ret.append( name );
00556         // following lines commented out for bugfixing kolab issue #177:
00557         //
00558         // Unlike we thought previously we may NOT append the server name here.
00559         //
00560         // The right server is found by the SMTP server instead: Kolab users
00561         // must use the correct SMTP server, by definition.
00562         //
00563         //mail = (*it1).client->base().simplifyWhiteSpace();
00564         //mail.replace( ",dc=", ".", false );
00565         //if( mail.startsWith("dc=", false) )
00566         //  mail.remove(0, 3);
00567         //mail.prepend( '@' );
00568         //mail.prepend( name );
00569         //mail = name;
00570       } else {
00571         kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl;
00572         continue; // nothing, bad entry
00573       }
00574     } else if ( name.isEmpty() ) {
00575       kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl;
00576       ret.append( mail );
00577     } else {
00578       kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\"  mail: \"" << mail << "\"" << endl;
00579       ret.append( QString( "%1 <%2>" ).arg( name ).arg( mail ) );
00580     }
00581 
00582     LdapResult sr;
00583     sr.clientNumber = (*it1).client->clientNumber();
00584     sr.completionWeight = (*it1).client->completionWeight();
00585     sr.name = name;
00586     sr.email = mails;
00587     resList.append( sr );
00588   }
00589 
00590   mResults.clear();
00591 }
00592 
00593 bool LdapSearch::isAvailable() const
00594 {
00595   return !mNoLDAPLookup;
00596 }
00597 
00598 
00599 #include "ldapclient.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys