File indexing completed on 2025-03-02 04:55:46

0001 /*
0002   This file is part of libkldap.
0003   SPDX-FileCopyrightText: 2004-2006 Szombathelyi György <gyurco@freemail.hu>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "ldapsearch.h"
0009 #include "ldapdefs.h"
0010 #include "ldapdn.h"
0011 
0012 #include <QTimer>
0013 
0014 #include "ldap_core_debug.h"
0015 #include <KLocalizedString>
0016 using namespace KLDAPCore;
0017 
0018 // blocking the GUI for xxx milliseconds
0019 #define LDAPSEARCH_BLOCKING_TIMEOUT 10
0020 
0021 class LdapSearchPrivate
0022 {
0023 public:
0024     explicit LdapSearchPrivate(LdapSearch *parent)
0025         : mParent(parent)
0026     {
0027     }
0028 
0029     void result();
0030     bool connect();
0031     void closeConnection();
0032     bool startSearch(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attributes, int pagesize, int count);
0033 
0034     LdapSearch *const mParent;
0035     LdapConnection *mConn = nullptr;
0036     LdapOperation mOp;
0037     bool mOwnConnection = false;
0038     bool mAbandoned = false;
0039     int mId;
0040     int mPageSize;
0041     LdapDN mBase;
0042     QString mFilter;
0043     QStringList mAttributes;
0044     LdapUrl::Scope mScope;
0045 
0046     QString mErrorString;
0047     int mError;
0048     int mCount;
0049     int mMaxCount;
0050     bool mFinished = false;
0051 };
0052 
0053 void LdapSearchPrivate::result()
0054 {
0055     if (mAbandoned) {
0056         mOp.abandon(mId);
0057         return;
0058     }
0059     const int res = mOp.waitForResult(mId, LDAPSEARCH_BLOCKING_TIMEOUT);
0060 
0061     qCDebug(LDAP_LOG) << "LDAP result:" << res;
0062 
0063     if (res != 0 && (res == -1 || (mConn->ldapErrorCode() != KLDAP_SUCCESS && mConn->ldapErrorCode() != KLDAP_SASL_BIND_IN_PROGRESS))) {
0064         // error happened, but no timeout
0065         mError = mConn->ldapErrorCode();
0066         mErrorString = mConn->ldapErrorString();
0067         Q_EMIT mParent->result(mParent);
0068         return;
0069     }
0070 
0071     // binding
0072     if (res == LdapOperation::RES_BIND) {
0073         const QByteArray servercc = mOp.serverCred();
0074 
0075         qCDebug(LDAP_LOG) << "LdapSearch RES_BIND";
0076         if (mConn->ldapErrorCode() == KLDAP_SUCCESS) { // bind succeeded
0077             qCDebug(LDAP_LOG) << "bind succeeded";
0078             LdapControls savedctrls = mOp.serverControls();
0079             if (mPageSize) {
0080                 LdapControls ctrls = savedctrls;
0081                 LdapControl::insert(ctrls, LdapControl::createPageControl(mPageSize));
0082                 mOp.setServerControls(ctrls);
0083             }
0084 
0085             mId = mOp.search(mBase, mScope, mFilter, mAttributes);
0086             mOp.setServerControls(savedctrls);
0087         } else { // next bind step
0088             qCDebug(LDAP_LOG) << "bind next step";
0089             mId = mOp.bind(servercc);
0090         }
0091         if (mId < 0) {
0092             if (mId == KLDAP_SASL_ERROR) {
0093                 mError = mId;
0094                 mErrorString = mConn->saslErrorString();
0095             } else {
0096                 mError = mConn->ldapErrorCode();
0097                 mErrorString = mConn->ldapErrorString();
0098             }
0099             Q_EMIT mParent->result(mParent);
0100             return;
0101         }
0102         QTimer::singleShot(0, mParent, [this]() {
0103             result();
0104         });
0105         return;
0106     }
0107 
0108     // End of entries
0109     if (res == LdapOperation::RES_SEARCH_RESULT) {
0110         if (mPageSize) {
0111             QByteArray cookie;
0112             int estsize = -1;
0113             const int numberOfControls(mOp.controls().count());
0114             for (int i = 0; i < numberOfControls; ++i) {
0115                 estsize = mOp.controls().at(i).parsePageControl(cookie);
0116                 if (estsize != -1) {
0117                     break;
0118                 }
0119             }
0120             qCDebug(LDAP_LOG) << " estimated size:" << estsize;
0121             if (estsize != -1 && !cookie.isEmpty()) {
0122                 LdapControls ctrls;
0123                 LdapControls savedctrls;
0124                 savedctrls = mOp.serverControls();
0125                 ctrls = savedctrls;
0126                 LdapControl::insert(ctrls, LdapControl::createPageControl(mPageSize, cookie));
0127                 mOp.setServerControls(ctrls);
0128                 mId = mOp.search(mBase, mScope, mFilter, mAttributes);
0129                 mOp.setServerControls(savedctrls);
0130                 if (mId == -1) {
0131                     mError = mConn->ldapErrorCode();
0132                     mErrorString = mConn->ldapErrorString();
0133                     Q_EMIT mParent->result(mParent);
0134                     return;
0135                 }
0136                 // continue with the next page
0137                 QTimer::singleShot(0, mParent, [this]() {
0138                     result();
0139                 });
0140                 return;
0141             }
0142         }
0143         mFinished = true;
0144         Q_EMIT mParent->result(mParent);
0145         return;
0146     }
0147 
0148     // Found an entry
0149     if (res == LdapOperation::RES_SEARCH_ENTRY) {
0150         Q_EMIT mParent->data(mParent, mOp.object());
0151         mCount++;
0152     }
0153 
0154     // If not reached the requested entries, continue
0155     if (mMaxCount <= 0 || mCount < mMaxCount) {
0156         QTimer::singleShot(0, mParent, [this]() {
0157             result();
0158         });
0159     }
0160     // If reached the requested entries, indicate it
0161     if (mMaxCount > 0 && mCount == mMaxCount) {
0162         qCDebug(LDAP_LOG) << mCount << " entries reached";
0163         Q_EMIT mParent->result(mParent);
0164     }
0165 }
0166 
0167 bool LdapSearchPrivate::connect()
0168 {
0169     const int ret = mConn->connect();
0170     if (ret != KLDAP_SUCCESS) {
0171         mError = ret;
0172         mErrorString = mConn->connectionError();
0173         closeConnection();
0174         return false;
0175     }
0176     return true;
0177 }
0178 
0179 void LdapSearchPrivate::closeConnection()
0180 {
0181     if (mOwnConnection && mConn) {
0182         delete mConn;
0183         mConn = nullptr;
0184     }
0185 }
0186 
0187 // This starts the real job
0188 bool LdapSearchPrivate::startSearch(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attributes, int pagesize, int count)
0189 {
0190     qCDebug(LDAP_LOG) << "search: base=" << base.toString() << "scope=" << static_cast<int>(scope) << "filter=" << filter << "attributes=" << attributes
0191                       << "pagesize=" << pagesize;
0192     mAbandoned = false;
0193     mError = 0;
0194     mErrorString.clear();
0195     mOp.setConnection(*mConn);
0196     mPageSize = pagesize;
0197     mBase = base;
0198     mScope = scope;
0199     mFilter = filter;
0200     mAttributes = attributes;
0201     mMaxCount = count;
0202     mCount = 0;
0203     mFinished = false;
0204 
0205     LdapControls savedctrls = mOp.serverControls();
0206     if (pagesize) {
0207         LdapControls ctrls = savedctrls;
0208         mConn->setOption(0x0008, nullptr); // Disable referals or paging won't work
0209         LdapControl::insert(ctrls, LdapControl::createPageControl(pagesize));
0210         mOp.setServerControls(ctrls);
0211     }
0212 
0213     mId = mOp.bind();
0214     if (mId < 0) {
0215         if (mId == KLDAP_SASL_ERROR) {
0216             mError = mId;
0217             mErrorString = mConn->saslErrorString();
0218         } else {
0219             mError = mConn->ldapErrorCode();
0220             mErrorString = mConn->ldapErrorString();
0221             if (mError == -1 && mErrorString.isEmpty()) {
0222                 mErrorString = i18n("Cannot access to server. Please reconfigure it.");
0223             }
0224         }
0225         return false;
0226     }
0227     qCDebug(LDAP_LOG) << "startSearch msg id=" << mId;
0228 
0229     // maybe do this with threads?- need thread-safe client libs!!!
0230     QTimer::singleShot(0, mParent, [this]() {
0231         result();
0232     });
0233 
0234     return true;
0235 }
0236 
0237 ///////////////////////////////////////////////
0238 
0239 LdapSearch::LdapSearch()
0240     : d(new LdapSearchPrivate(this))
0241 {
0242     d->mOwnConnection = true;
0243     d->mConn = nullptr;
0244 }
0245 
0246 LdapSearch::LdapSearch(LdapConnection &connection)
0247     : d(new LdapSearchPrivate(this))
0248 {
0249     d->mOwnConnection = false;
0250     d->mConn = &connection;
0251 }
0252 
0253 LdapSearch::~LdapSearch()
0254 {
0255     d->closeConnection();
0256 }
0257 
0258 void LdapSearch::setConnection(LdapConnection &connection)
0259 {
0260     d->closeConnection();
0261     d->mOwnConnection = false;
0262     d->mConn = &connection;
0263 }
0264 
0265 void LdapSearch::setClientControls(const LdapControls &ctrls)
0266 {
0267     d->mOp.setClientControls(ctrls);
0268 }
0269 
0270 void LdapSearch::setServerControls(const LdapControls &ctrls)
0271 {
0272     d->mOp.setServerControls(ctrls);
0273 }
0274 
0275 bool LdapSearch::search(const LdapServer &server, const QStringList &attributes, int count)
0276 {
0277     if (d->mOwnConnection) {
0278         d->closeConnection();
0279         d->mConn = new LdapConnection(server);
0280         if (!d->connect()) {
0281             return false;
0282         }
0283     }
0284     return d->startSearch(server.baseDn(), server.scope(), server.filter(), attributes, server.pageSize(), count);
0285 }
0286 
0287 bool LdapSearch::search(const LdapUrl &url, int count)
0288 {
0289     if (d->mOwnConnection) {
0290         d->closeConnection();
0291         d->mConn = new LdapConnection(url);
0292         if (!d->connect()) {
0293             return false;
0294         }
0295     }
0296     bool critical = true;
0297     const int pagesize = url.extension(QStringLiteral("x-pagesize"), critical).toInt();
0298     return d->startSearch(url.dn(), url.scope(), url.filter(), url.attributes(), pagesize, count);
0299 }
0300 
0301 bool LdapSearch::search(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attributes, int pagesize, int count)
0302 {
0303     Q_ASSERT(!d->mOwnConnection);
0304     return d->startSearch(base, scope, filter, attributes, pagesize, count);
0305 }
0306 
0307 void LdapSearch::continueSearch()
0308 {
0309     Q_ASSERT(!d->mFinished);
0310     d->mCount = 0;
0311     QTimer::singleShot(0, this, [this]() {
0312         d->result();
0313     });
0314 }
0315 
0316 bool LdapSearch::isFinished()
0317 {
0318     return d->mFinished;
0319 }
0320 
0321 void LdapSearch::abandon()
0322 {
0323     d->mAbandoned = true;
0324 }
0325 
0326 int LdapSearch::error() const
0327 {
0328     return d->mError;
0329 }
0330 
0331 QString LdapSearch::errorString() const
0332 {
0333     return d->mErrorString;
0334 }
0335 
0336 #include "moc_ldapsearch.cpp"