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::readWeighForClient( LdapClient *client, KConfig *config, int clientNumber )
00353 {
00354   const int completionWeight = config->readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( clientNumber ), -1 );
00355   if ( completionWeight != -1 )
00356     client->setCompletionWeight( completionWeight );
00357 }
00358 
00359 void LdapSearch::updateCompletionWeights()
00360 {
00361   KConfig *config = KPIM::LdapSearch::config();
00362   config->setGroup( "LDAP" );
00363   for ( uint i = 0; i < mClients.size(); i++ ) {
00364     readWeighForClient( mClients[i], config, i );
00365   }
00366 }
00367 
00368 void LdapSearch::readConfig()
00369 {
00370   cancelSearch();
00371   QValueList< LdapClient* >::Iterator it;
00372   for ( it = mClients.begin(); it != mClients.end(); ++it )
00373     delete *it;
00374   mClients.clear();
00375 
00376   // stolen from KAddressBook
00377   KConfig *config = KPIM::LdapSearch::config();
00378   config->setGroup( "LDAP" );
00379   int numHosts = config->readUnsignedNumEntry( "NumSelectedHosts");
00380   if ( !numHosts ) {
00381     mNoLDAPLookup = true;
00382   } else {
00383     for ( int j = 0; j < numHosts; j++ ) {
00384       LdapClient* ldapClient = new LdapClient( j, this );
00385       LdapServer server;
00386       readConfig( server, config, j, true );
00387       if ( !server.host().isEmpty() ) mNoLDAPLookup = false;
00388       ldapClient->setServer( server );
00389 
00390       readWeighForClient( ldapClient, config, j );
00391 
00392       QStringList attrs;
00393       // note: we need "objectClass" to detect distribution lists
00394       attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass";
00395       ldapClient->setAttrs( attrs );
00396 
00397       connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ),
00398                this, SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) );
00399       connect( ldapClient, SIGNAL( done() ),
00400                this, SLOT( slotLDAPDone() ) );
00401       connect( ldapClient, SIGNAL( error( const QString& ) ),
00402                this, SLOT( slotLDAPError( const QString& ) ) );
00403 
00404       mClients.append( ldapClient );
00405     }
00406 
00407     connect( &mDataTimer, SIGNAL( timeout() ), SLOT( slotDataTimer() ) );
00408   }
00409   mConfigFile = locateLocal( "config", "kabldaprc" );
00410   KDirWatch::self()->addFile( mConfigFile );
00411 }
00412 
00413 void LdapSearch::slotFileChanged( const QString& file )
00414 {
00415   if ( file == mConfigFile )
00416     readConfig();
00417 }
00418 
00419 void LdapSearch::startSearch( const QString& txt )
00420 {
00421   if ( mNoLDAPLookup )
00422     return;
00423 
00424   cancelSearch();
00425 
00426   int pos = txt.find( '\"' );
00427   if( pos >= 0 )
00428   {
00429     ++pos;
00430     int pos2 = txt.find( '\"', pos );
00431     if( pos2 >= 0 )
00432         mSearchText = txt.mid( pos , pos2 - pos );
00433     else
00434         mSearchText = txt.mid( pos );
00435   } else
00436     mSearchText = txt;
00437 
00438   /* The reasoning behind this filter is:
00439    * If it's a person, or a distlist, show it, even if it doesn't have an email address.
00440    * If it's not a person, or a distlist, only show it if it has an email attribute.
00441    * This allows both resource accounts with an email address which are not a person and
00442    * person entries without an email address to show up, while still not showing things
00443    * like structural entries in the ldap tree. */
00444   QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(mail=*@%3*)(givenName=%4*)(sn=%5*))" )
00445       .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText );
00446 
00447   QValueList< LdapClient* >::Iterator it;
00448   for ( it = mClients.begin(); it != mClients.end(); ++it ) {
00449     (*it)->startQuery( filter );
00450     kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl;
00451     ++mActiveClients;
00452   }
00453 }
00454 
00455 void LdapSearch::cancelSearch()
00456 {
00457   QValueList< LdapClient* >::Iterator it;
00458   for ( it = mClients.begin(); it != mClients.end(); ++it )
00459     (*it)->cancelQuery();
00460 
00461   mActiveClients = 0;
00462   mResults.clear();
00463 }
00464 
00465 void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj )
00466 {
00467   mResults.append( obj );
00468   if ( !mDataTimer.isActive() )
00469     mDataTimer.start( 500, true );
00470 }
00471 
00472 void LdapSearch::slotLDAPError( const QString& )
00473 {
00474   slotLDAPDone();
00475 }
00476 
00477 void LdapSearch::slotLDAPDone()
00478 {
00479   if ( --mActiveClients > 0 )
00480     return;
00481 
00482   finish();
00483 }
00484 
00485 void LdapSearch::slotDataTimer()
00486 {
00487   QStringList lst;
00488   LdapResultList reslist;
00489   makeSearchData( lst, reslist );
00490   if ( !lst.isEmpty() )
00491     emit searchData( lst );
00492   if ( !reslist.isEmpty() )
00493     emit searchData( reslist );
00494 }
00495 
00496 void LdapSearch::finish()
00497 {
00498   mDataTimer.stop();
00499 
00500   slotDataTimer(); // emit final bunch of data
00501   emit searchDone();
00502 }
00503 
00504 void LdapSearch::makeSearchData( QStringList& ret, LdapResultList& resList )
00505 {
00506   QString search_text_upper = mSearchText.upper();
00507 
00508   QValueList< KPIM::LdapObject >::ConstIterator it1;
00509   for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) {
00510     QString name, mail, givenname, sn;
00511     QStringList mails;
00512     bool isDistributionList = false;
00513     bool wasCN = false;
00514     bool wasDC = false;
00515 
00516     //kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl;
00517 
00518     LdapAttrMap::ConstIterator it2;
00519     for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) {
00520       QByteArray val = (*it2).first();
00521       int len = val.size();
00522       if( len > 0 && '\0' == val[len-1] )
00523         --len;
00524       const QString tmp = QString::fromUtf8( val, len );
00525       //kdDebug(5300) << "      key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl;
00526       if ( it2.key() == "cn" ) {
00527         name = tmp;
00528         if( mail.isEmpty() )
00529           mail = tmp;
00530         else{
00531           if( wasCN )
00532             mail.prepend( "." );
00533           else
00534             mail.prepend( "@" );
00535           mail.prepend( tmp );
00536         }
00537         wasCN = true;
00538       } else if ( it2.key() == "dc" ) {
00539         if( mail.isEmpty() )
00540           mail = tmp;
00541         else{
00542           if( wasDC )
00543             mail.append( "." );
00544           else
00545             mail.append( "@" );
00546           mail.append( tmp );
00547         }
00548         wasDC = true;
00549       } else if( it2.key() == "mail" ) {
00550         mail = tmp;
00551         LdapAttrValue::ConstIterator it3 = it2.data().begin();
00552         for ( ; it3 != it2.data().end(); ++it3 ) {
00553           mails.append( QString::fromUtf8( (*it3).data(), (*it3).size() ) );
00554         }
00555       } else if( it2.key() == "givenName" )
00556         givenname = tmp;
00557       else if( it2.key() == "sn" )
00558         sn = tmp;
00559       else if( it2.key() == "objectClass" &&
00560                (tmp == "groupOfNames" || tmp == "kolabGroupOfNames") ) {
00561         isDistributionList = true;
00562       }
00563     }
00564 
00565     if( mails.isEmpty()) {
00566       if ( !mail.isEmpty() ) mails.append( mail );
00567       if( isDistributionList ) {
00568         //kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl;
00569         ret.append( name );
00570         // following lines commented out for bugfixing kolab issue #177:
00571         //
00572         // Unlike we thought previously we may NOT append the server name here.
00573         //
00574         // The right server is found by the SMTP server instead: Kolab users
00575         // must use the correct SMTP server, by definition.
00576         //
00577         //mail = (*it1).client->base().simplifyWhiteSpace();
00578         //mail.replace( ",dc=", ".", false );
00579         //if( mail.startsWith("dc=", false) )
00580         //  mail.remove(0, 3);
00581         //mail.prepend( '@' );
00582         //mail.prepend( name );
00583         //mail = name;
00584       } else {
00585         //kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl;
00586         continue; // nothing, bad entry
00587       }
00588     } else if ( name.isEmpty() ) {
00589       //kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl;
00590       ret.append( mail );
00591     } else {
00592       //kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\"  mail: \"" << mail << "\"" << endl;
00593       ret.append( QString( "%1 <%2>" ).arg( name ).arg( mail ) );
00594     }
00595 
00596     LdapResult sr;
00597     sr.clientNumber = (*it1).client->clientNumber();
00598     sr.completionWeight = (*it1).client->completionWeight();
00599     sr.name = name;
00600     sr.email = mails;
00601     resList.append( sr );
00602   }
00603 
00604   mResults.clear();
00605 }
00606 
00607 bool LdapSearch::isAvailable() const
00608 {
00609   return !mNoLDAPLookup;
00610 }
00611 
00612 
00613 #include "ldapclient.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys