libtdepim

ldapclient.cpp
1/* kldapclient.cpp - LDAP access
2 * Copyright (C) 2002 Klarälvdalens Datakonsult AB
3 *
4 * Author: Steffen Hansen <hansen@kde.org>
5 *
6 * Ported to KABC by Daniel Molkentin <molkentin@kde.org>
7 *
8 * This file is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This file is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23
24
25#include <tqfile.h>
26#include <tqimage.h>
27#include <tqlabel.h>
28#include <tqpixmap.h>
29#include <tqtextstream.h>
30#include <tqurl.h>
31
32#include <tdeabc/ldapurl.h>
33#include <tdeabc/ldif.h>
34#include <tdeapplication.h>
35#include <tdeconfig.h>
36#include <kdebug.h>
37#include <kdirwatch.h>
38#include <kmdcodec.h>
39#include <kprotocolinfo.h>
40#include <tdestandarddirs.h>
41#include <kstaticdeleter.h>
42
43#include "ldapclient.h"
44
45using namespace KPIM;
46
47TDEConfig *KPIM::LdapSearch::s_config = 0L;
48static KStaticDeleter<TDEConfig> configDeleter;
49
50TQString LdapObject::toString() const
51{
52 TQString result = TQString::fromLatin1( "\ndn: %1\n" ).arg( dn );
53 for ( LdapAttrMap::ConstIterator it = attrs.begin(); it != attrs.end(); ++it ) {
54 TQString attr = it.key();
55 for ( LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2 ) {
56 result += TQString::fromUtf8( TDEABC::LDIF::assembleLine( attr, *it2, 76 ) ) + "\n";
57 }
58 }
59
60 return result;
61}
62
63void LdapObject::clear()
64{
65 dn = TQString();
66 objectClass = TQString();
67 attrs.clear();
68}
69
70void LdapObject::assign( const LdapObject& that )
71{
72 if ( &that != this ) {
73 dn = that.dn;
74 attrs = that.attrs;
75 client = that.client;
76 }
77}
78
79LdapClient::LdapClient( int clientNumber, TQObject* parent, const char* name )
80 : TQObject( parent, name ), mJob( 0 ), mActive( false ), mReportObjectClass( false )
81{
82// d = new LdapClientPrivate;
83 mClientNumber = clientNumber;
84 mCompletionWeight = 50 - mClientNumber;
85}
86
87LdapClient::~LdapClient()
88{
90// delete d; d = 0;
91}
92
93void LdapClient::setAttrs( const TQStringList& attrs )
94{
95 mAttrs = attrs;
96 for ( TQStringList::Iterator it = mAttrs.begin(); it != mAttrs.end(); ++it )
97 if( (*it).lower() == "objectclass" ){
98 mReportObjectClass = true;
99 return;
100 }
101 mAttrs << "objectClass"; // via objectClass we detect distribution lists
102 mReportObjectClass = false;
103}
104
105void LdapClient::startQuery( const TQString& filter )
106{
107 cancelQuery();
108 TDEABC::LDAPUrl url;
109
110 url.setProtocol( ( mServer.security() == LdapServer::SSL ) ? "ldaps" : "ldap" );
111 if ( mServer.auth() != LdapServer::Anonymous ) {
112 url.setUser( mServer.user() );
113 url.setPass( mServer.pwdBindDN() );
114 }
115 url.setHost( mServer.host() );
116 url.setPort( mServer.port() );
117 url.setExtension( "x-ver", TQString::number( mServer.version() ) );
118 url.setDn( mServer.baseDN() );
119 url.setDn( mServer.baseDN() );
120 if ( mServer.security() == LdapServer::TLS ) url.setExtension( "x-tls","" );
121 if ( mServer.auth() == LdapServer::SASL ) {
122 url.setExtension( "x-sasl","" );
123 if ( !mServer.bindDN().isEmpty() ) url.setExtension( "x-bindname", mServer.bindDN() );
124 if ( !mServer.mech().isEmpty() ) url.setExtension( "x-mech", mServer.mech() );
125 }
126 if ( mServer.timeLimit() != 0 ) url.setExtension( "x-timelimit",
127 TQString::number( mServer.timeLimit() ) );
128 if ( mServer.sizeLimit() != 0 ) url.setExtension( "x-sizelimit",
129 TQString::number( mServer.sizeLimit() ) );
130
131 url.setAttributes( mAttrs );
132 url.setScope( mScope == "one" ? TDEABC::LDAPUrl::One : TDEABC::LDAPUrl::Sub );
133 url.setFilter( "("+filter+")" );
134
135 kdDebug(5300) << "LdapClient: Doing query: " << url.prettyURL() << endl;
136
137 startParseLDIF();
138 mActive = true;
139 mJob = TDEIO::get( url, false, false );
140 connect( mJob, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray& ) ),
141 this, TQ_SLOT( slotData( TDEIO::Job*, const TQByteArray& ) ) );
142 connect( mJob, TQ_SIGNAL( infoMessage( TDEIO::Job*, const TQString& ) ),
143 this, TQ_SLOT( slotInfoMessage( TDEIO::Job*, const TQString& ) ) );
144 connect( mJob, TQ_SIGNAL( result( TDEIO::Job* ) ),
145 this, TQ_SLOT( slotDone() ) );
146}
147
149{
150 if ( mJob ) {
151 mJob->kill();
152 mJob = 0;
153 }
154
155 mActive = false;
156}
157
158void LdapClient::slotData( TDEIO::Job*, const TQByteArray& data )
159{
160 parseLDIF( data );
161}
162
163void LdapClient::slotInfoMessage( TDEIO::Job*, const TQString & )
164{
165 //tqDebug("Job said \"%s\"", info.latin1());
166}
167
168void LdapClient::slotDone()
169{
170 endParseLDIF();
171 mActive = false;
172#if 0
173 for ( TQValueList<LdapObject>::Iterator it = mObjects.begin(); it != mObjects.end(); ++it ) {
174 tqDebug( (*it).toString().latin1() );
175 }
176#endif
177 int err = mJob->error();
178 if ( err && err != TDEIO::ERR_USER_CANCELED ) {
179 emit error( mJob->errorString() );
180 }
181 emit done();
182}
183
184void LdapClient::startParseLDIF()
185{
186 mCurrentObject.clear();
187 mLdif.startParsing();
188}
189
190void LdapClient::endParseLDIF()
191{
192}
193
194void LdapClient::finishCurrentObject()
195{
196 mCurrentObject.dn = mLdif.dn();
197 const TQString sClass( mCurrentObject.objectClass.lower() );
198 if( sClass == "groupofnames" || sClass == "kolabgroupofnames" ){
199 LdapAttrMap::ConstIterator it = mCurrentObject.attrs.find("mail");
200 if( it == mCurrentObject.attrs.end() ){
201 // No explicit mail address found so far?
202 // Fine, then we use the address stored in the DN.
203 TQString sMail;
204 TQStringList lMail = TQStringList::split(",dc=", mCurrentObject.dn);
205 const int n = lMail.count();
206 if( n ){
207 if( lMail.first().lower().startsWith("cn=") ){
208 sMail = lMail.first().simplifyWhiteSpace().mid(3);
209 if( 1 < n )
210 sMail.append('@');
211 for( int i=1; i<n; ++i){
212 sMail.append( lMail[i] );
213 if( i < n-1 )
214 sMail.append('.');
215 }
216 mCurrentObject.attrs["mail"].append( sMail.utf8() );
217 }
218 }
219 }
220 }
221 mCurrentObject.client = this;
222 emit result( mCurrentObject );
223 mCurrentObject.clear();
224}
225
226void LdapClient::parseLDIF( const TQByteArray& data )
227{
228 //kdDebug(5300) << "LdapClient::parseLDIF( " << TQCString(data.data(), data.size()+1) << " )" << endl;
229 if ( data.size() ) {
230 mLdif.setLDIF( data );
231 } else {
232 mLdif.endLDIF();
233 }
234
235 TDEABC::LDIF::ParseVal ret;
236 TQString name;
237 do {
238 ret = mLdif.nextItem();
239 switch ( ret ) {
240 case TDEABC::LDIF::Item:
241 {
242 name = mLdif.attr();
243 // Must make a copy! TQByteArray is explicitely shared
244 TQByteArray value = mLdif.val().copy();
245 bool bIsObjectClass = name.lower() == "objectclass";
246 if( bIsObjectClass )
247 mCurrentObject.objectClass = TQString::fromUtf8( value, value.size() );
248 if( mReportObjectClass || !bIsObjectClass )
249 mCurrentObject.attrs[ name ].append( value );
250 //kdDebug(5300) << "LdapClient::parseLDIF(): name=" << name << " value=" << TQCString(value.data(), value.size()+1) << endl;
251 }
252 break;
253 case TDEABC::LDIF::EndEntry:
254 finishCurrentObject();
255 break;
256 default:
257 break;
258 }
259 } while ( ret != TDEABC::LDIF::MoreData );
260}
261
262int LdapClient::clientNumber() const
263{
264 return mClientNumber;
265}
266
267int LdapClient::completionWeight() const
268{
269 return mCompletionWeight;
270}
271
272void LdapClient::setCompletionWeight( int weight )
273{
274 mCompletionWeight = weight;
275}
276
277void LdapSearch::readConfig( LdapServer &server, TDEConfig *config, int j, bool active )
278{
279 TQString prefix;
280 if ( active ) prefix = "Selected";
281 TQString host = config->readEntry( prefix + TQString( "Host%1" ).arg( j ), "" ).stripWhiteSpace();
282 if ( !host.isEmpty() )
283 server.setHost( host );
284
285 int port = config->readNumEntry( prefix + TQString( "Port%1" ).arg( j ), 389 );
286 server.setPort( port );
287
288 TQString base = config->readEntry( prefix + TQString( "Base%1" ).arg( j ), "" ).stripWhiteSpace();
289 if ( !base.isEmpty() )
290 server.setBaseDN( base );
291
292 TQString user = config->readEntry( prefix + TQString( "User%1" ).arg( j ) ).stripWhiteSpace();
293 if ( !user.isEmpty() )
294 server.setUser( user );
295
296 TQString bindDN = config->readEntry( prefix + TQString( "Bind%1" ).arg( j ) ).stripWhiteSpace();
297 if ( !bindDN.isEmpty() )
298 server.setBindDN( bindDN );
299
300 TQString pwdBindDN = config->readEntry( prefix + TQString( "PwdBind%1" ).arg( j ) );
301 if ( !pwdBindDN.isEmpty() )
302 server.setPwdBindDN( pwdBindDN );
303
304 server.setTimeLimit( config->readNumEntry( prefix + TQString( "TimeLimit%1" ).arg( j ) ) );
305 server.setSizeLimit( config->readNumEntry( prefix + TQString( "SizeLimit%1" ).arg( j ) ) );
306 server.setVersion( config->readNumEntry( prefix + TQString( "Version%1" ).arg( j ), 3 ) );
307 server.setSecurity( config->readNumEntry( prefix + TQString( "Security%1" ).arg( j ) ) );
308 server.setAuth( config->readNumEntry( prefix + TQString( "Auth%1" ).arg( j ) ) );
309 server.setMech( config->readEntry( prefix + TQString( "Mech%1" ).arg( j ) ) );
310}
311
312void LdapSearch::writeConfig( const LdapServer &server, TDEConfig *config, int j, bool active )
313{
314 TQString prefix;
315 if ( active ) prefix = "Selected";
316 config->writeEntry( prefix + TQString( "Host%1" ).arg( j ), server.host() );
317 config->writeEntry( prefix + TQString( "Port%1" ).arg( j ), server.port() );
318 config->writeEntry( prefix + TQString( "Base%1" ).arg( j ), server.baseDN() );
319 config->writeEntry( prefix + TQString( "User%1" ).arg( j ), server.user() );
320 config->writeEntry( prefix + TQString( "Bind%1" ).arg( j ), server.bindDN() );
321 config->writeEntry( prefix + TQString( "PwdBind%1" ).arg( j ), server.pwdBindDN() );
322 config->writeEntry( prefix + TQString( "TimeLimit%1" ).arg( j ), server.timeLimit() );
323 config->writeEntry( prefix + TQString( "SizeLimit%1" ).arg( j ), server.sizeLimit() );
324 config->writeEntry( prefix + TQString( "Version%1" ).arg( j ), server.version() );
325 config->writeEntry( prefix + TQString( "Security%1" ).arg( j ), server.security() );
326 config->writeEntry( prefix + TQString( "Auth%1" ).arg( j ), server.auth() );
327 config->writeEntry( prefix + TQString( "Mech%1" ).arg( j ), server.mech() );
328}
329
330TDEConfig* LdapSearch::config()
331{
332 if ( !s_config )
333 configDeleter.setObject( s_config, new TDEConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals
334
335 return s_config;
336}
337
338
339LdapSearch::LdapSearch()
340 : mActiveClients( 0 ), mNoLDAPLookup( false )
341{
342 if ( !KProtocolInfo::isKnownProtocol( KURL("ldap://localhost") ) ) {
343 mNoLDAPLookup = true;
344 return;
345 }
346
347 readConfig();
348 connect(KDirWatch::self(), TQ_SIGNAL(dirty (const TQString&)),this,
349 TQ_SLOT(slotFileChanged(const TQString&)));
350}
351
352void LdapSearch::readWeighForClient( LdapClient *client, TDEConfig *config, int clientNumber )
353{
354 const int completionWeight = config->readNumEntry( TQString( "SelectedCompletionWeight%1" ).arg( clientNumber ), -1 );
355 if ( completionWeight != -1 )
356 client->setCompletionWeight( completionWeight );
357}
358
359void LdapSearch::updateCompletionWeights()
360{
361 TDEConfig *config = KPIM::LdapSearch::config();
362 config->setGroup( "LDAP" );
363 for ( uint i = 0; i < mClients.size(); i++ ) {
364 readWeighForClient( mClients[i], config, i );
365 }
366}
367
368void LdapSearch::readConfig()
369{
370 cancelSearch();
371 TQValueList< LdapClient* >::Iterator it;
372 for ( it = mClients.begin(); it != mClients.end(); ++it )
373 delete *it;
374 mClients.clear();
375
376 // stolen from KAddressBook
377 TDEConfig *config = KPIM::LdapSearch::config();
378 config->setGroup( "LDAP" );
379 int numHosts = config->readUnsignedNumEntry( "NumSelectedHosts");
380 if ( !numHosts ) {
381 mNoLDAPLookup = true;
382 } else {
383 for ( int j = 0; j < numHosts; j++ ) {
384 LdapClient* ldapClient = new LdapClient( j, this );
385 LdapServer server;
386 readConfig( server, config, j, true );
387 if ( !server.host().isEmpty() ) mNoLDAPLookup = false;
388 ldapClient->setServer( server );
389
390 readWeighForClient( ldapClient, config, j );
391
392 TQStringList attrs;
393 // note: we need "objectClass" to detect distribution lists
394 attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass";
395 ldapClient->setAttrs( attrs );
396
397 connect( ldapClient, TQ_SIGNAL( result( const KPIM::LdapObject& ) ),
398 this, TQ_SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) );
399 connect( ldapClient, TQ_SIGNAL( done() ),
400 this, TQ_SLOT( slotLDAPDone() ) );
401 connect( ldapClient, TQ_SIGNAL( error( const TQString& ) ),
402 this, TQ_SLOT( slotLDAPError( const TQString& ) ) );
403
404 mClients.append( ldapClient );
405 }
406
407 connect( &mDataTimer, TQ_SIGNAL( timeout() ), TQ_SLOT( slotDataTimer() ) );
408 }
409 mConfigFile = locateLocal( "config", "kabldaprc" );
410 KDirWatch::self()->addFile( mConfigFile );
411}
412
413void LdapSearch::slotFileChanged( const TQString& file )
414{
415 if ( file == mConfigFile )
416 readConfig();
417}
418
419void LdapSearch::startSearch( const TQString& txt )
420{
421 if ( mNoLDAPLookup )
422 return;
423
424 cancelSearch();
425
426 int pos = txt.find( '\"' );
427 if( pos >= 0 )
428 {
429 ++pos;
430 int pos2 = txt.find( '\"', pos );
431 if( pos2 >= 0 )
432 mSearchText = txt.mid( pos , pos2 - pos );
433 else
434 mSearchText = txt.mid( pos );
435 } else
436 mSearchText = txt;
437
438 /* The reasoning behind this filter is:
439 * If it's a person, or a distlist, show it, even if it doesn't have an email address.
440 * If it's not a person, or a distlist, only show it if it has an email attribute.
441 * This allows both resource accounts with an email address which are not a person and
442 * person entries without an email address to show up, while still not showing things
443 * like structural entries in the ldap tree. */
444 TQString filter = TQString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(mail=*@%3*)(givenName=%4*)(sn=%5*))" )
445 .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText );
446
447 TQValueList< LdapClient* >::Iterator it;
448 for ( it = mClients.begin(); it != mClients.end(); ++it ) {
449 (*it)->startQuery( filter );
450 kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl;
451 ++mActiveClients;
452 }
453}
454
455void LdapSearch::cancelSearch()
456{
457 TQValueList< LdapClient* >::Iterator it;
458 for ( it = mClients.begin(); it != mClients.end(); ++it )
459 (*it)->cancelQuery();
460
461 mActiveClients = 0;
462 mResults.clear();
463}
464
465void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj )
466{
467 mResults.append( obj );
468 if ( !mDataTimer.isActive() )
469 mDataTimer.start( 500, true );
470}
471
472void LdapSearch::slotLDAPError( const TQString& )
473{
474 slotLDAPDone();
475}
476
477void LdapSearch::slotLDAPDone()
478{
479 if ( --mActiveClients > 0 )
480 return;
481
482 finish();
483}
484
485void LdapSearch::slotDataTimer()
486{
487 TQStringList lst;
488 LdapResultList reslist;
489 makeSearchData( lst, reslist );
490 if ( !lst.isEmpty() )
491 emit searchData( lst );
492 if ( !reslist.isEmpty() )
493 emit searchData( reslist );
494}
495
496void LdapSearch::finish()
497{
498 mDataTimer.stop();
499
500 slotDataTimer(); // emit final bunch of data
501 emit searchDone();
502}
503
504void LdapSearch::makeSearchData( TQStringList& ret, LdapResultList& resList )
505{
506 TQString search_text_upper = mSearchText.upper();
507
508 TQValueList< KPIM::LdapObject >::ConstIterator it1;
509 for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) {
510 TQString name, mail, givenname, sn;
511 TQStringList mails;
512 bool isDistributionList = false;
513 bool wasCN = false;
514 bool wasDC = false;
515
516 //kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl;
517
518 LdapAttrMap::ConstIterator it2;
519 for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) {
520 TQByteArray val = (*it2).first();
521 int len = val.size();
522 if( len > 0 && '\0' == val[len-1] )
523 --len;
524 const TQString tmp = TQString::fromUtf8( val, len );
525 //kdDebug(5300) << " key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl;
526 if ( it2.key() == "cn" ) {
527 name = tmp;
528 if( mail.isEmpty() )
529 mail = tmp;
530 else{
531 if( wasCN )
532 mail.prepend( "." );
533 else
534 mail.prepend( "@" );
535 mail.prepend( tmp );
536 }
537 wasCN = true;
538 } else if ( it2.key() == "dc" ) {
539 if( mail.isEmpty() )
540 mail = tmp;
541 else{
542 if( wasDC )
543 mail.append( "." );
544 else
545 mail.append( "@" );
546 mail.append( tmp );
547 }
548 wasDC = true;
549 } else if( it2.key() == "mail" ) {
550 mail = tmp;
551 LdapAttrValue::ConstIterator it3 = it2.data().begin();
552 for ( ; it3 != it2.data().end(); ++it3 ) {
553 mails.append( TQString::fromUtf8( (*it3).data(), (*it3).size() ) );
554 }
555 } else if( it2.key() == "givenName" )
556 givenname = tmp;
557 else if( it2.key() == "sn" )
558 sn = tmp;
559 else if( it2.key() == "objectClass" &&
560 (tmp == "groupOfNames" || tmp == "kolabGroupOfNames") ) {
561 isDistributionList = true;
562 }
563 }
564
565 if( mails.isEmpty()) {
566 if ( !mail.isEmpty() ) mails.append( mail );
567 if( isDistributionList ) {
568 //kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl;
569 ret.append( name );
570 // following lines commented out for bugfixing kolab issue #177:
571 //
572 // Unlike we thought previously we may NOT append the server name here.
573 //
574 // The right server is found by the SMTP server instead: Kolab users
575 // must use the correct SMTP server, by definition.
576 //
577 //mail = (*it1).client->base().simplifyWhiteSpace();
578 //mail.replace( ",dc=", ".", false );
579 //if( mail.startsWith("dc=", false) )
580 // mail.remove(0, 3);
581 //mail.prepend( '@' );
582 //mail.prepend( name );
583 //mail = name;
584 } else {
585 //kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl;
586 continue; // nothing, bad entry
587 }
588 } else if ( name.isEmpty() ) {
589 //kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl;
590 ret.append( mail );
591 } else {
592 //kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\" mail: \"" << mail << "\"" << endl;
593 ret.append( TQString( "%1 <%2>" ).arg( name ).arg( mail ) );
594 }
595
596 LdapResult sr;
597 sr.clientNumber = (*it1).client->clientNumber();
598 sr.completionWeight = (*it1).client->completionWeight();
599 sr.name = name;
600 sr.email = mails;
601 resList.append( sr );
602 }
603
604 mResults.clear();
605}
606
607bool LdapSearch::isAvailable() const
608{
609 return !mNoLDAPLookup;
610}
611
612
613#include "ldapclient.moc"
This class is internal.
Definition: ldapclient.h:143
void result(const KPIM::LdapObject &)
void setAttrs(const TQStringList &attrs)
Definition: ldapclient.cpp:93
TQStringList attrs() const
Definition: ldapclient.h:164
void error(const TQString &)
void startQuery(const TQString &filter)
Definition: ldapclient.cpp:105
This class is internal.
Definition: ldapclient.h:106
void searchData(const TQStringList &)
Results, assembled as "Full Name <email>" (This signal can be emitted many times)
TDEPIM classes for drag and drop of mails.
Structure describing one result returned by a LDAP query.
Definition: ldapclient.h:230
TQString name
full name
Definition: ldapclient.h:231
TQStringList email
emails
Definition: ldapclient.h:232
int completionWeight
for sorting in a completion list
Definition: ldapclient.h:234
int clientNumber
for sorting in a ldap-only lookup
Definition: ldapclient.h:233