File indexing completed on 2025-01-26 04:52:15
0001 /* kldapclient.cpp - LDAP access 0002 * SPDX-FileCopyrightText: 2002 Klarälvdalens Datakonsult AB 0003 * SPDX-FileContributor: Steffen Hansen <hansen@kde.org> 0004 * 0005 * Ported to KABC by Daniel Molkentin <molkentin@kde.org> 0006 * 0007 * SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org> 0008 * 0009 * SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "ldapclientsearch.h" 0013 #include "ldapclient_debug.h" 0014 #include "ldapclientsearchconfig.h" 0015 #include "ldapsearchclientreadconfigserverjob.h" 0016 0017 #include "ldapclient.h" 0018 0019 #include "kldapcore/ldapserver.h" 0020 #include "kldapcore/ldapurl.h" 0021 #include "kldapcore/ldif.h" 0022 0023 #include <KConfig> 0024 #include <KConfigGroup> 0025 #include <KDirWatch> 0026 #include <KProtocolInfo> 0027 0028 #include <KIO/Job> 0029 0030 #include <QStandardPaths> 0031 #include <QTimer> 0032 0033 using namespace KLDAPWidgets; 0034 0035 class Q_DECL_HIDDEN LdapClientSearch::LdapClientSearchPrivate 0036 { 0037 public: 0038 LdapClientSearchPrivate(LdapClientSearch *qq) 0039 : q(qq) 0040 { 0041 } 0042 0043 ~LdapClientSearchPrivate() = default; 0044 0045 void readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber); 0046 void readConfig(); 0047 void finish(); 0048 void makeSearchData(QStringList &ret, LdapResult::List &resList); 0049 0050 void slotLDAPResult(const KLDAPWidgets::LdapClient &client, const KLDAPCore::LdapObject &); 0051 void slotLDAPError(const QString &); 0052 void slotLDAPDone(); 0053 void slotDataTimer(); 0054 void slotFileChanged(const QString &); 0055 void init(const QStringList &attributes); 0056 0057 LdapClientSearch *const q; 0058 QList<LdapClient *> mClients; 0059 QStringList mAttributes; 0060 QString mSearchText; 0061 QString mFilter; 0062 QTimer mDataTimer; 0063 int mActiveClients = 0; 0064 bool mNoLDAPLookup = false; 0065 LdapResultObject::List mResults; 0066 QString mConfigFile; 0067 }; 0068 0069 LdapClientSearch::LdapClientSearch(QObject *parent) 0070 : QObject(parent) 0071 , d(new LdapClientSearchPrivate(this)) 0072 { 0073 d->init(LdapClientSearch::defaultAttributes()); 0074 } 0075 0076 LdapClientSearch::LdapClientSearch(const QStringList &attr, QObject *parent) 0077 : QObject(parent) 0078 , d(new LdapClientSearchPrivate(this)) 0079 { 0080 d->init(attr); 0081 } 0082 0083 LdapClientSearch::~LdapClientSearch() = default; 0084 0085 void LdapClientSearch::LdapClientSearchPrivate::init(const QStringList &attributes) 0086 { 0087 if (!KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("ldap://localhost")))) { 0088 mNoLDAPLookup = true; 0089 return; 0090 } 0091 0092 mAttributes = attributes; 0093 0094 // Set the filter, to make sure old usage (before 4.14) of this object still works. 0095 mFilter = QStringLiteral( 0096 "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))" 0097 "(|(cn=%1*)(mail=%1*)(givenName=%1*)(sn=%1*))"); 0098 0099 readConfig(); 0100 q->connect(KDirWatch::self(), &KDirWatch::dirty, q, [this](const QString &filename) { 0101 slotFileChanged(filename); 0102 }); 0103 } 0104 0105 void LdapClientSearch::LdapClientSearchPrivate::readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber) 0106 { 0107 const int completionWeight = config.readEntry(QStringLiteral("SelectedCompletionWeight%1").arg(clientNumber), -1); 0108 if (completionWeight != -1) { 0109 client->setCompletionWeight(completionWeight); 0110 } 0111 } 0112 0113 void LdapClientSearch::updateCompletionWeights() 0114 { 0115 KConfigGroup config(KLDAPWidgets::LdapClientSearchConfig::config(), QStringLiteral("LDAP")); 0116 for (int i = 0, total = d->mClients.size(); i < total; ++i) { 0117 d->readWeighForClient(d->mClients[i], config, i); 0118 } 0119 } 0120 0121 QList<LdapClient *> LdapClientSearch::clients() const 0122 { 0123 return d->mClients; 0124 } 0125 0126 QString LdapClientSearch::filter() const 0127 { 0128 return d->mFilter; 0129 } 0130 0131 void LdapClientSearch::setFilter(const QString &filter) 0132 { 0133 d->mFilter = filter; 0134 } 0135 0136 QStringList LdapClientSearch::attributes() const 0137 { 0138 return d->mAttributes; 0139 } 0140 0141 void LdapClientSearch::setAttributes(const QStringList &attrs) 0142 { 0143 if (attrs != d->mAttributes) { 0144 d->mAttributes = attrs; 0145 d->readConfig(); 0146 } 0147 } 0148 0149 QStringList LdapClientSearch::defaultAttributes() 0150 { 0151 const QStringList attr{QStringLiteral("cn"), QStringLiteral("mail"), QStringLiteral("givenname"), QStringLiteral("sn")}; 0152 return attr; 0153 } 0154 0155 void LdapClientSearch::LdapClientSearchPrivate::readConfig() 0156 { 0157 q->cancelSearch(); 0158 qDeleteAll(mClients); 0159 mClients.clear(); 0160 0161 // stolen from KAddressBook 0162 KConfigGroup config(KLDAPWidgets::LdapClientSearchConfig::config(), QStringLiteral("LDAP")); 0163 const int numHosts = config.readEntry("NumSelectedHosts", 0); 0164 if (!numHosts) { 0165 mNoLDAPLookup = true; 0166 } else { 0167 for (int j = 0; j < numHosts; ++j) { 0168 auto ldapClient = new LdapClient(j, q); 0169 auto job = new LdapSearchClientReadConfigServerJob; 0170 job->setCurrentIndex(j); 0171 job->setActive(true); 0172 job->setConfig(config); 0173 job->setLdapClient(ldapClient); 0174 job->start(); 0175 0176 mNoLDAPLookup = false; 0177 readWeighForClient(ldapClient, config, j); 0178 0179 ldapClient->setAttributes(mAttributes); 0180 0181 q->connect(ldapClient, &LdapClient::result, q, [this](const LdapClient &client, const KLDAPCore::LdapObject &obj) { 0182 slotLDAPResult(client, obj); 0183 }); 0184 q->connect(ldapClient, &LdapClient::done, q, [this]() { 0185 slotLDAPDone(); 0186 }); 0187 q->connect(ldapClient, qOverload<const QString &>(&LdapClient::error), q, [this](const QString &str) { 0188 slotLDAPError(str); 0189 }); 0190 0191 mClients.append(ldapClient); 0192 } 0193 0194 q->connect(&mDataTimer, &QTimer::timeout, q, [this]() { 0195 slotDataTimer(); 0196 }); 0197 } 0198 mConfigFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kabldaprc"); 0199 KDirWatch::self()->addFile(mConfigFile); 0200 } 0201 0202 void LdapClientSearch::LdapClientSearchPrivate::slotFileChanged(const QString &file) 0203 { 0204 if (file == mConfigFile) { 0205 readConfig(); 0206 } 0207 } 0208 0209 void LdapClientSearch::startSearch(const QString &txt) 0210 { 0211 if (d->mNoLDAPLookup) { 0212 QMetaObject::invokeMethod(this, &LdapClientSearch::searchDone, Qt::QueuedConnection); 0213 return; 0214 } 0215 0216 cancelSearch(); 0217 0218 int pos = txt.indexOf(QLatin1Char('\"')); 0219 if (pos >= 0) { 0220 ++pos; 0221 const int pos2 = txt.indexOf(QLatin1Char('\"'), pos); 0222 if (pos2 >= 0) { 0223 d->mSearchText = txt.mid(pos, pos2 - pos); 0224 } else { 0225 d->mSearchText = txt.mid(pos); 0226 } 0227 } else { 0228 d->mSearchText = txt; 0229 } 0230 0231 const QString filter = d->mFilter.arg(d->mSearchText); 0232 0233 QList<LdapClient *>::Iterator it(d->mClients.begin()); 0234 const QList<LdapClient *>::Iterator end(d->mClients.end()); 0235 for (; it != end; ++it) { 0236 (*it)->startQuery(filter); 0237 qCDebug(LDAPCLIENT_LOG) << "LdapClientSearch::startSearch()" << filter; 0238 ++d->mActiveClients; 0239 } 0240 } 0241 0242 void LdapClientSearch::cancelSearch() 0243 { 0244 QList<LdapClient *>::Iterator it(d->mClients.begin()); 0245 const QList<LdapClient *>::Iterator end(d->mClients.end()); 0246 for (; it != end; ++it) { 0247 (*it)->cancelQuery(); 0248 } 0249 0250 d->mActiveClients = 0; 0251 d->mResults.clear(); 0252 } 0253 0254 void LdapClientSearch::LdapClientSearchPrivate::slotLDAPResult(const LdapClient &client, const KLDAPCore::LdapObject &obj) 0255 { 0256 LdapResultObject result; 0257 result.client = &client; 0258 result.object = obj; 0259 0260 mResults.append(result); 0261 if (!mDataTimer.isActive()) { 0262 mDataTimer.setSingleShot(true); 0263 mDataTimer.start(500); 0264 } 0265 } 0266 0267 void LdapClientSearch::LdapClientSearchPrivate::slotLDAPError(const QString &) 0268 { 0269 slotLDAPDone(); 0270 } 0271 0272 void LdapClientSearch::LdapClientSearchPrivate::slotLDAPDone() 0273 { 0274 if (--mActiveClients > 0) { 0275 return; 0276 } 0277 0278 finish(); 0279 } 0280 0281 void LdapClientSearch::LdapClientSearchPrivate::slotDataTimer() 0282 { 0283 QStringList lst; 0284 LdapResult::List reslist; 0285 0286 Q_EMIT q->searchData(mResults); 0287 0288 makeSearchData(lst, reslist); 0289 if (!lst.isEmpty()) { 0290 Q_EMIT q->searchData(lst); 0291 } 0292 if (!reslist.isEmpty()) { 0293 Q_EMIT q->searchData(reslist); 0294 } 0295 } 0296 0297 void LdapClientSearch::LdapClientSearchPrivate::finish() 0298 { 0299 mDataTimer.stop(); 0300 0301 slotDataTimer(); // Q_EMIT final bunch of data 0302 Q_EMIT q->searchDone(); 0303 } 0304 0305 void LdapClientSearch::LdapClientSearchPrivate::makeSearchData(QStringList &ret, KLDAPWidgets::LdapResult::List &resList) 0306 { 0307 LdapResultObject::List::ConstIterator it1(mResults.constBegin()); 0308 const LdapResultObject::List::ConstIterator end1(mResults.constEnd()); 0309 for (; it1 != end1; ++it1) { 0310 QString name; 0311 QString mail; 0312 QString givenname; 0313 QString sn; 0314 QStringList mails; 0315 bool isDistributionList = false; 0316 bool wasCN = false; 0317 bool wasDC = false; 0318 0319 // qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData()"; 0320 0321 KLDAPCore::LdapAttrMap::ConstIterator it2; 0322 for (it2 = (*it1).object.attributes().constBegin(); it2 != (*it1).object.attributes().constEnd(); ++it2) { 0323 QByteArray val = (*it2).first(); 0324 int len = val.size(); 0325 if (len > 0 && '\0' == val[len - 1]) { 0326 --len; 0327 } 0328 const QString tmp = QString::fromUtf8(val.constData(), len); 0329 // qCDebug(LDAPCLIENT_LOG) <<" key: \"" << it2.key() <<"\" value: \"" << tmp <<"\""; 0330 if (it2.key() == QLatin1StringView("cn")) { 0331 name = tmp; 0332 if (mail.isEmpty()) { 0333 mail = tmp; 0334 } else { 0335 if (wasCN) { 0336 mail.prepend(QLatin1Char('.')); 0337 } else { 0338 mail.prepend(QLatin1Char('@')); 0339 } 0340 mail.prepend(tmp); 0341 } 0342 wasCN = true; 0343 } else if (it2.key() == QLatin1StringView("dc")) { 0344 if (mail.isEmpty()) { 0345 mail = tmp; 0346 } else { 0347 if (wasDC) { 0348 mail.append(QLatin1Char('.')); 0349 } else { 0350 mail.append(QLatin1Char('@')); 0351 } 0352 mail.append(tmp); 0353 } 0354 wasDC = true; 0355 } else if (it2.key() == QLatin1StringView("mail")) { 0356 mail = tmp; 0357 KLDAPCore::LdapAttrValue::ConstIterator it3 = it2.value().constBegin(); 0358 for (; it3 != it2.value().constEnd(); ++it3) { 0359 mails.append(QString::fromUtf8((*it3).data(), (*it3).size())); 0360 } 0361 } else if (it2.key() == QLatin1StringView("givenName")) { 0362 givenname = tmp; 0363 } else if (it2.key() == QLatin1StringView("sn")) { 0364 sn = tmp; 0365 } else if (it2.key() == QLatin1StringView("objectClass") 0366 && (tmp == QLatin1StringView("groupOfNames") || tmp == QLatin1StringView("kolabGroupOfNames"))) { 0367 isDistributionList = true; 0368 } 0369 } 0370 0371 if (mails.isEmpty()) { 0372 if (!mail.isEmpty()) { 0373 mails.append(mail); 0374 } 0375 if (isDistributionList) { 0376 // qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData() found a list:" << name; 0377 ret.append(name); 0378 // following lines commented out for bugfixing kolab issue #177: 0379 // 0380 // Unlike we thought previously we may NOT append the server name here. 0381 // 0382 // The right server is found by the SMTP server instead: Kolab users 0383 // must use the correct SMTP server, by definition. 0384 // 0385 // mail = (*it1).client->base().simplified(); 0386 // mail.replace( ",dc=", ".", false ); 0387 // if( mail.startsWith("dc=", false) ) 0388 // mail.remove(0, 3); 0389 // mail.prepend( '@' ); 0390 // mail.prepend( name ); 0391 // mail = name; 0392 } else { 0393 continue; // nothing, bad entry 0394 } 0395 } else if (name.isEmpty()) { 0396 ret.append(mail); 0397 } else { 0398 ret.append(QStringLiteral("%1 <%2>").arg(name, mail)); 0399 } 0400 0401 LdapResult sr; 0402 sr.dn = (*it1).object.dn(); 0403 sr.clientNumber = (*it1).client->clientNumber(); 0404 sr.completionWeight = (*it1).client->completionWeight(); 0405 sr.name = name; 0406 sr.email = mails; 0407 resList.append(sr); 0408 } 0409 0410 mResults.clear(); 0411 } 0412 0413 bool LdapClientSearch::isAvailable() const 0414 { 0415 return !d->mNoLDAPLookup; 0416 } 0417 0418 #include "moc_ldapclientsearch.cpp"