00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
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
00083 mClientNumber = clientNumber;
00084 mCompletionWeight = 50 - mClientNumber;
00085 }
00086
00087 LdapClient::~LdapClient()
00088 {
00089 cancelQuery();
00090
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";
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
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
00202
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
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
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
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 ) );
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
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
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
00425
00426
00427
00428
00429
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();
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
00557
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570 } else {
00571 kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl;
00572 continue;
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"