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"