1/*
2 This file is part of libkldap.
3 Copyright (c) 2004-2006 Szombathelyi György <gyurco@freemail.hu>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "ldapsearch.h"
22#include "ldapdn.h"
23#include "ldapdefs.h"
24
25#include <QtCore/QEventLoop>
26#include <QtCore/QTimer>
27
28#include <kdebug.h>
29#include <KLocalizedString>
30using namespace KLDAP;
31
32//blocking the GUI for xxx milliseconds
33#define LDAPSEARCH_BLOCKING_TIMEOUT 10
34
35class LdapSearch::Private
36{
37 public:
38 Private( LdapSearch *parent )
39 : mParent( parent )
40 {
41 }
42
43 void result();
44 bool connect();
45 void closeConnection();
46 bool startSearch( const LdapDN &base, LdapUrl::Scope scope,
47 const QString &filter, const QStringList &attributes,
48 int pagesize, int count );
49
50 LdapSearch *mParent;
51 LdapConnection *mConn;
52 LdapOperation mOp;
53 bool mOwnConnection, mAbandoned;
54 int mId, mPageSize;
55 LdapDN mBase;
56 QString mFilter;
57 QStringList mAttributes;
58 LdapUrl::Scope mScope;
59
60 QString mErrorString;
61 int mError;
62 int mCount, mMaxCount;
63 bool mFinished;
64};
65
66void LdapSearch::Private::result()
67{
68 if ( mAbandoned ) {
69 mOp.abandon( mId );
70 return;
71 }
72 int res = mOp.waitForResult( mId, LDAPSEARCH_BLOCKING_TIMEOUT );
73
74 kDebug() << "LDAP result:" << res;
75
76 if ( res != 0 &&
77 ( res == -1 ||
78 ( mConn->ldapErrorCode() != KLDAP_SUCCESS &&
79 mConn->ldapErrorCode() != KLDAP_SASL_BIND_IN_PROGRESS ) ) ) {
80 //error happened, but no timeout
81 mError = mConn->ldapErrorCode();
82 mErrorString = mConn->ldapErrorString();
83 emit mParent->result( mParent );
84 return;
85 }
86
87 //binding
88 if ( res == LdapOperation::RES_BIND ) {
89
90 QByteArray servercc;
91 servercc = mOp.serverCred();
92
93 kDebug() << "LdapSearch RES_BIND";
94 if ( mConn->ldapErrorCode() == KLDAP_SUCCESS ) { //bind succeeded
95 kDebug() << "bind succeeded";
96 LdapControls savedctrls = mOp.serverControls();
97 if ( mPageSize ) {
98 LdapControls ctrls = savedctrls;
99 LdapControl::insert( ctrls, LdapControl::createPageControl( mPageSize ) );
100 mOp.setServerControls( ctrls );
101 }
102
103 mId = mOp.search( mBase, mScope, mFilter, mAttributes );
104 mOp.setServerControls( savedctrls );
105 } else { //next bind step
106 kDebug() << "bind next step";
107 mId = mOp.bind( servercc );
108 }
109 if ( mId < 0 ) {
110 if ( mId == KLDAP_SASL_ERROR ) {
111 mError = mId;
112 mErrorString = mConn->saslErrorString();
113 } else {
114 mError = mConn->ldapErrorCode();
115 mErrorString = mConn->ldapErrorString();
116 }
117 emit mParent->result( mParent );
118 return;
119 }
120 QTimer::singleShot( 0, mParent, SLOT(result()) );
121 return;
122 }
123
124 //End of entries
125 if ( res == LdapOperation::RES_SEARCH_RESULT ) {
126 if ( mPageSize ) {
127 QByteArray cookie;
128 int estsize = -1;
129 const int numberOfControls( mOp.controls().count() );
130 for ( int i = 0; i < numberOfControls; ++i ) {
131 estsize = mOp.controls()[i].parsePageControl( cookie );
132 if ( estsize != -1 ) {
133 break;
134 }
135 }
136 kDebug() << " estimated size:" << estsize;
137 if ( estsize != -1 && !cookie.isEmpty() ) {
138 LdapControls ctrls, savedctrls;
139 savedctrls = mOp.serverControls();
140 ctrls = savedctrls;
141 LdapControl::insert( ctrls, LdapControl::createPageControl ( mPageSize, cookie ) );
142 mOp.setServerControls( ctrls );
143 mId = mOp.search( mBase, mScope, mFilter, mAttributes );
144 mOp.setServerControls( savedctrls );
145 if ( mId == -1 ) {
146 mError = mConn->ldapErrorCode();
147 mErrorString = mConn->ldapErrorString();
148 emit mParent->result( mParent );
149 return;
150 }
151 //continue with the next page
152 QTimer::singleShot( 0, mParent, SLOT(result()) );
153 return;
154 }
155 }
156 mFinished = true;
157 emit mParent->result( mParent );
158 return;
159 }
160
161 //Found an entry
162 if ( res == LdapOperation::RES_SEARCH_ENTRY ) {
163 emit mParent->data( mParent, mOp.object() );
164 mCount++;
165 }
166
167 //If not reached the requested entries, continue
168 if ( mMaxCount <= 0 || mCount < mMaxCount ) {
169 QTimer::singleShot( 0, mParent, SLOT(result()) );
170 }
171 //If reached the requested entries, indicate it
172 if ( mMaxCount > 0 && mCount == mMaxCount ) {
173 kDebug() << mCount << " entries reached";
174 emit mParent->result( mParent );
175 }
176}
177
178bool LdapSearch::Private::connect()
179{
180 int ret = mConn->connect();
181 if ( ret != KLDAP_SUCCESS ) {
182 mError = ret;
183 mErrorString = mConn->connectionError();
184 closeConnection();
185 return false;
186 }
187 return true;
188}
189
190void LdapSearch::Private::closeConnection()
191{
192 if ( mOwnConnection && mConn ) {
193 delete mConn;
194 mConn = 0;
195 }
196}
197
198//This starts the real job
199bool LdapSearch::Private::startSearch( const LdapDN &base, LdapUrl::Scope scope,
200 const QString &filter,
201 const QStringList &attributes, int pagesize, int count )
202{
203 kDebug() << "search: base=" << base.toString() << "scope=" << (int)scope
204 << "filter=" << filter << "attributes=" << attributes
205 << "pagesize=" << pagesize;
206 mAbandoned = false;
207 mError = 0;
208 mErrorString.clear();
209 mOp.setConnection( *mConn );
210 mPageSize = pagesize;
211 mBase = base;
212 mScope = scope;
213 mFilter = filter;
214 mAttributes = attributes;
215 mMaxCount = count;
216 mCount = 0;
217 mFinished = false;
218
219 LdapControls savedctrls = mOp.serverControls();
220 if ( pagesize ) {
221 LdapControls ctrls = savedctrls;
222 mConn->setOption( 0x0008, NULL ); // Disable referals or paging won't work
223 LdapControl::insert( ctrls, LdapControl::createPageControl( pagesize ) );
224 mOp.setServerControls( ctrls );
225 }
226
227 mId = mOp.bind();
228 if ( mId < 0 ) {
229 if ( mId == KLDAP_SASL_ERROR ) {
230 mError = mId;
231 mErrorString = mConn->saslErrorString();
232 } else {
233 mError = mConn->ldapErrorCode();
234 mErrorString = mConn->ldapErrorString();
235 if ( mError == -1 && mErrorString.isEmpty() ) {
236 mErrorString = i18n( "Cannot access to server. Please reconfigure it." );
237 }
238 }
239 return false;
240 }
241 kDebug() << "startSearch msg id=" << mId;
242
243 //maybe do this with threads?- need thread-safe client libs!!!
244 QTimer::singleShot( 0, mParent, SLOT(result()) );
245
246 return true;
247}
248
249///////////////////////////////////////////////
250
251LdapSearch::LdapSearch()
252 : d( new Private( this ) )
253{
254 d->mOwnConnection = true;
255 d->mConn = 0;
256}
257
258LdapSearch::LdapSearch( LdapConnection &connection )
259 : d( new Private( this ) )
260{
261 d->mOwnConnection = false;
262 d->mConn = &connection;
263}
264
265LdapSearch::~LdapSearch()
266{
267 d->closeConnection();
268 delete d;
269}
270
271void LdapSearch::setConnection( LdapConnection &connection )
272{
273 d->closeConnection();
274 d->mOwnConnection = false;
275 d->mConn = &connection;
276}
277
278void LdapSearch::setClientControls( const LdapControls &ctrls )
279{
280 d->mOp.setClientControls( ctrls );
281}
282
283void LdapSearch::setServerControls( const LdapControls &ctrls )
284{
285 d->mOp.setServerControls( ctrls );
286}
287
288bool LdapSearch::search( const LdapServer &server,
289 const QStringList &attributes, int count )
290{
291 if ( d->mOwnConnection ) {
292 d->closeConnection();
293 d->mConn = new LdapConnection( server );
294 if ( !d->connect() ) {
295 return false;
296 }
297 }
298 return d->startSearch( server.baseDn(), server.scope(), server.filter(),
299 attributes, server.pageSize(), count );
300}
301
302bool LdapSearch::search( const LdapUrl &url, int count )
303{
304 if ( d->mOwnConnection ) {
305 d->closeConnection();
306 d->mConn = new LdapConnection( url );
307 if ( !d->connect() ) {
308 return false;
309 }
310 }
311 bool critical = true;
312 int pagesize = url.extension( QLatin1String( "x-pagesize" ), critical ).toInt();
313 return d->startSearch( url.dn(), url.scope(), url.filter(),
314 url.attributes(), pagesize, count );
315}
316
317bool LdapSearch::search( const LdapDN &base, LdapUrl::Scope scope,
318 const QString &filter, const QStringList &attributes,
319 int pagesize, int count )
320{
321 Q_ASSERT( !d->mOwnConnection );
322 return d->startSearch( base, scope, filter, attributes, pagesize, count );
323}
324
325void LdapSearch::continueSearch()
326{
327 Q_ASSERT( !d->mFinished );
328 d->mCount = 0;
329 QTimer::singleShot( 0, this, SLOT(result()) );
330}
331
332bool LdapSearch::isFinished()
333{
334 return d->mFinished;
335}
336
337void LdapSearch::abandon()
338{
339 d->mAbandoned = true;
340}
341
342int LdapSearch::error() const
343{
344 return d->mError;
345}
346
347QString LdapSearch::errorString() const
348{
349 return d->mErrorString;
350}
351
352#include "moc_ldapsearch.cpp"
353