KLdap

ldapoperation.cpp
1/*
2 This file is part of libkldap.
3 SPDX-FileCopyrightText: 2004-2006 Szombathelyi György <gyurco@freemail.hu>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "ldapoperation.h"
9#include "kldap_config.h"
10
11#include "ldap_core_debug.h"
12
13#include <QElapsedTimer>
14
15#include <cstdlib>
16
17// for struct timeval
18#if HAVE_SYS_TIME_H
19#include <sys/time.h>
20#elif defined(_WIN32)
21#include <winsock2.h>
22#endif
23
24#include <sasl/sasl.h>
25
26#if LDAP_FOUND
27#if !HAVE_WINLDAP_H
28#include <lber.h>
29#include <ldap.h>
30#else
31#include "w32-ldap-help.h"
32#endif // HAVE_WINLDAP_H
33#endif // LDAP_FOUND
34
35#include "ldapdefs.h"
36
37using namespace KLDAPCore;
38
39#if LDAP_FOUND
40static void extractControls(LdapControls &ctrls, LDAPControl **pctrls);
41#endif // LDAP_FOUND
42
43/*
44 Returns the difference between msecs and elapsed. If msecs is -1,
45 however, -1 is returned.
46*/
47static int kldap_timeout_value(int msecs, int elapsed)
48{
49 if (msecs == -1) {
50 return -1;
51 }
52
53 int timeout = msecs - elapsed;
54 return timeout < 0 ? 0 : timeout;
55}
56
57class Q_DECL_HIDDEN LdapOperation::LdapOperationPrivate
58{
59public:
60 LdapOperationPrivate();
61 ~LdapOperationPrivate();
62#if LDAP_FOUND
63 int processResult(int rescode, LDAPMessage *msg);
64 int bind(const QByteArray &creds, SASL_Callback_Proc *saslproc, void *data, bool async);
65#endif
66 LdapControls mClientCtrls, mServerCtrls, mControls;
67 LdapObject mObject;
68 QByteArray mExtOid, mExtData;
69 QByteArray mServerCred;
70 QString mMatchedDn;
71 QList<QByteArray> mReferrals;
72
73 LdapConnection *mConnection = nullptr;
74};
75
76LdapOperation::LdapOperation()
77 : d(new LdapOperationPrivate)
78{
79 d->mConnection = nullptr;
80}
81
82LdapOperation::LdapOperation(LdapConnection &conn)
83 : d(new LdapOperationPrivate)
84{
85 setConnection(conn);
86}
87
88LdapOperation::~LdapOperation() = default;
89
91{
92 d->mConnection = &conn;
93}
94
96{
97 return *d->mConnection;
98}
99
101{
102 d->mClientCtrls = ctrls;
103}
104
106{
107 d->mServerCtrls = ctrls;
108}
109
111{
112 return d->mClientCtrls;
113}
114
116{
117 return d->mServerCtrls;
118}
119
121{
122 return d->mObject;
123}
124
126{
127 return d->mControls;
128}
129
131{
132 return d->mExtOid;
133}
134
136{
137 return d->mExtData;
138}
139
141{
142 return d->mMatchedDn;
143}
144
146{
147 return d->mReferrals;
148}
149
151{
152 return d->mServerCred;
153}
154
155LdapOperation::LdapOperationPrivate::LdapOperationPrivate() = default;
156
157LdapOperation::LdapOperationPrivate::~LdapOperationPrivate() = default;
158
159#if LDAP_FOUND
160
161static int kldap_sasl_interact(sasl_interact_t *interact, LdapOperation::SASL_Data *data)
162{
163 if (data->proc) {
164 for (; interact->id != SASL_CB_LIST_END; interact++) {
165 switch (interact->id) {
166 case SASL_CB_GETREALM:
167 data->creds.fields |= LdapOperation::SASL_Realm;
168 break;
169 case SASL_CB_AUTHNAME:
170 data->creds.fields |= LdapOperation::SASL_Authname;
171 break;
172 case SASL_CB_PASS:
173 data->creds.fields |= LdapOperation::SASL_Password;
174 break;
175 case SASL_CB_USER:
176 data->creds.fields |= LdapOperation::SASL_Authzid;
177 break;
178 }
179 }
180 int retval;
181 if ((retval = data->proc(data->creds, data->data))) {
182 return retval;
183 }
184 }
185
186 QString value;
187
188 while (interact->id != SASL_CB_LIST_END) {
189 value.clear();
190 switch (interact->id) {
191 case SASL_CB_GETREALM:
192 value = data->creds.realm;
193 qCDebug(LDAP_CORE_LOG) << "SASL_REALM=" << value;
194 break;
195 case SASL_CB_AUTHNAME:
196 value = data->creds.authname;
197 qCDebug(LDAP_CORE_LOG) << "SASL_AUTHNAME=" << value;
198 break;
199 case SASL_CB_PASS:
200 value = data->creds.password;
201 qCDebug(LDAP_CORE_LOG) << "SASL_PASSWD=[hidden]";
202 break;
203 case SASL_CB_USER:
204 value = data->creds.authzid;
205 qCDebug(LDAP_CORE_LOG) << "SASL_AUTHZID=" << value;
206 break;
207 }
208 if (value.isEmpty()) {
209 interact->result = nullptr;
210 interact->len = 0;
211 } else {
212 interact->result = strdup(value.toUtf8().constData());
213 interact->len = strlen((const char *)interact->result);
214 }
215 interact++;
216 }
217 return KLDAP_SUCCESS;
218}
219
220int LdapOperation::LdapOperationPrivate::bind(const QByteArray &creds, SASL_Callback_Proc *saslproc, void *data, bool async)
221{
222 Q_ASSERT(mConnection);
223 LDAP *ld = (LDAP *)mConnection->handle();
224 LdapServer server;
225 server = mConnection->server();
226
227 int ret;
228
229 if (server.auth() == LdapServer::SASL) {
230#if !HAVE_WINLDAP_H
231 auto saslconn = (sasl_conn_t *)mConnection->saslHandle();
232 sasl_interact_t *client_interact = nullptr;
233 const char *out = nullptr;
234 uint outlen;
235 const char *mechusing = nullptr;
236 struct berval ccred;
237 struct berval *scred;
238 int saslresult;
239 QByteArray sdata = creds;
240
241 QString mech = server.mech();
242 if (mech.isEmpty()) {
243 mech = QStringLiteral("DIGEST-MD5");
244 }
245
246 SASL_Data sasldata;
247 sasldata.proc = saslproc;
248 sasldata.data = data;
249 sasldata.creds.fields = 0;
250 sasldata.creds.realm = server.realm();
251 sasldata.creds.authname = server.user();
252 sasldata.creds.authzid = server.bindDn();
253 sasldata.creds.password = server.password();
254
255 do {
256 if (sdata.isEmpty()) {
257 do {
258 saslresult = sasl_client_start(saslconn, mech.toLatin1().constData(), &client_interact, &out, &outlen, &mechusing);
259
260 if (saslresult == SASL_INTERACT) {
261 if (kldap_sasl_interact(client_interact, &sasldata) != KLDAP_SUCCESS) {
262 return KLDAP_SASL_ERROR;
263 }
264 }
265 qCDebug(LDAP_CORE_LOG) << "sasl_client_start mech: " << mechusing << " outlen " << outlen << " result: " << saslresult;
266 } while (saslresult == SASL_INTERACT);
267 if (saslresult != SASL_CONTINUE && saslresult != SASL_OK) {
268 return KLDAP_SASL_ERROR;
269 }
270 } else {
271 qCDebug(LDAP_CORE_LOG) << "sasl_client_step";
272 do {
273 saslresult = sasl_client_step(saslconn, sdata.data(), sdata.size(), &client_interact, &out, &outlen);
274 if (saslresult == SASL_INTERACT) {
275 if (kldap_sasl_interact(client_interact, &sasldata) != KLDAP_SUCCESS) {
276 return KLDAP_SASL_ERROR;
277 }
278 }
279 } while (saslresult == SASL_INTERACT);
280 qCDebug(LDAP_CORE_LOG) << "sasl_client_step result" << saslresult;
281 if (saslresult != SASL_CONTINUE && saslresult != SASL_OK) {
282 return KLDAP_SASL_ERROR;
283 }
284 }
285
286 ccred.bv_val = (char *)out;
287 ccred.bv_len = outlen;
288
289 if (async) {
290 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind";
291 int msgid;
292 ret = ldap_sasl_bind(ld, server.bindDn().toUtf8().constData(), mech.toLatin1().constData(), &ccred, nullptr, nullptr, &msgid);
293 if (ret == 0) {
294 ret = msgid;
295 }
296 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind msgid" << ret;
297 } else {
298 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind_s";
299 ret = ldap_sasl_bind_s(ld, server.bindDn().toUtf8().constData(), mech.toLatin1().constData(), &ccred, nullptr, nullptr, &scred);
300 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind_s ret" << ret;
301 if (scred) {
302 sdata = QByteArray(scred->bv_val, scred->bv_len);
303 } else {
304 sdata = QByteArray();
305 }
306 }
307 } while (!async && ret == KLDAP_SASL_BIND_IN_PROGRESS);
308#else
309 qCritical() << "SASL authentication is not available "
310 << "(re-compile kldap with cyrus-sasl and OpenLDAP development).";
311 return KLDAP_SASL_ERROR;
312#endif
313 } else { // simple auth
314 QByteArray bindname;
315 QByteArray pass;
316 struct berval ccred;
317 if (server.auth() == LdapServer::Simple) {
318 bindname = server.bindDn().toUtf8();
319 pass = server.password().toUtf8();
320 }
321 ccred.bv_val = pass.data();
322 ccred.bv_len = pass.size();
323 qCDebug(LDAP_CORE_LOG) << "binding to server, bindname: " << bindname << " password: *****";
324
325 if (async) {
326 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind (simple)";
327#if !HAVE_WINLDAP_H
328 int msgid = 0;
329 ret = ldap_sasl_bind(ld, bindname.data(), nullptr, &ccred, nullptr, nullptr, &msgid);
330 if (ret == 0) {
331 ret = msgid;
332 }
333#else
334 ret = ldap_simple_bind(ld, bindname.data(), pass.data());
335#endif
336 } else {
337 qCDebug(LDAP_CORE_LOG) << "ldap_sasl_bind_s (simple)";
338#if !HAVE_WINLDAP_H
339 ret = ldap_sasl_bind_s(ld, bindname.data(), nullptr, &ccred, nullptr, nullptr, nullptr);
340#else
341 ret = ldap_simple_bind_s(ld, bindname.data(), pass.data());
342#endif
343 }
344 }
345 return ret;
346}
347
348int LdapOperation::LdapOperationPrivate::processResult(int rescode, LDAPMessage *msg)
349{
350 // qCDebug(LDAP_CORE_LOG);
351 int retval;
352 LDAP *ld = (LDAP *)mConnection->handle();
353
354 qCDebug(LDAP_CORE_LOG) << "rescode: " << rescode;
355 switch (rescode) {
356 case RES_SEARCH_ENTRY: {
357 // qCDebug(LDAP_CORE_LOG) << "Found search entry";
358 mObject.clear();
359 LdapAttrMap attrs;
360 char *name;
361 struct berval **bvals;
362 BerElement *entry;
363 LdapAttrValue values;
364
365 char *dn = ldap_get_dn(ld, msg);
366 mObject.setDn(QString::fromUtf8(dn));
367 ldap_memfree(dn);
368
369 // iterate over the attributes
370 name = ldap_first_attribute(ld, msg, &entry);
371 while (name != nullptr) {
372 // print the values
373 bvals = ldap_get_values_len(ld, msg, name);
374 if (bvals) {
375 for (int i = 0; bvals[i] != nullptr; i++) {
376 char *val = bvals[i]->bv_val;
377 unsigned long len = bvals[i]->bv_len;
378 values.append(QByteArray(val, len));
379 }
380 ldap_value_free_len(bvals);
381 }
382 attrs[QString::fromLatin1(name)] = values;
383 values.clear();
384 ldap_memfree(name);
385
386 // next attribute
387 name = ldap_next_attribute(ld, msg, entry);
388 }
389 ber_free(entry, 0);
390 mObject.setAttributes(attrs);
391 break;
392 }
393 case RES_SEARCH_REFERENCE:
394 // Will only get this if following references is disabled. ignore it
395 rescode = 0;
396 break;
397 case RES_EXTENDED: {
398 char *retoid;
399 struct berval *retdata;
400 retval = ldap_parse_extended_result(ld, msg, &retoid, &retdata, 0);
401 if (retval != KLDAP_SUCCESS) {
402 ldap_msgfree(msg);
403 return -1;
404 }
405 mExtOid = retoid ? QByteArray(retoid) : QByteArray();
406 mExtData = retdata ? QByteArray(retdata->bv_val, retdata->bv_len) : QByteArray();
407 ldap_memfree(retoid);
408 ber_bvfree(retdata);
409 break;
410 }
411 case RES_BIND: {
412 struct berval *servercred = nullptr;
413#if !HAVE_WINLDAP_H
414 // FIXME: Error handling Winldap does not have ldap_parse_sasl_bind_result
415 retval = ldap_parse_sasl_bind_result(ld, msg, &servercred, 0);
416#else
417 retval = KLDAP_SUCCESS;
418#endif
419 if (retval != KLDAP_SUCCESS && retval != KLDAP_SASL_BIND_IN_PROGRESS) {
420 qCDebug(LDAP_CORE_LOG) << "RES_BIND error: " << retval;
421 ldap_msgfree(msg);
422 return -1;
423 }
424 qCDebug(LDAP_CORE_LOG) << "RES_BIND rescode" << rescode << "retval:" << retval;
425 if (servercred) {
426 mServerCred = QByteArray(servercred->bv_val, servercred->bv_len);
427 ber_bvfree(servercred);
428 } else {
429 mServerCred = QByteArray();
430 }
431 break;
432 }
433 default: {
434 LDAPControl **serverctrls = nullptr;
435 char *matcheddn = nullptr;
436 char *errmsg = nullptr;
437 char **referralsp;
438 int errcodep;
439 retval = ldap_parse_result(ld, msg, &errcodep, &matcheddn, &errmsg, &referralsp, &serverctrls, 0);
440 qCDebug(LDAP_CORE_LOG) << "rescode" << rescode << "retval:" << retval << "matcheddn:" << matcheddn << "errcode:" << errcodep << "errmsg:" << errmsg;
441 if (retval != KLDAP_SUCCESS) {
442 ldap_msgfree(msg);
443 return -1;
444 }
445 mControls.clear();
446 if (serverctrls) {
447 extractControls(mControls, serverctrls);
448 ldap_controls_free(serverctrls);
449 }
450 mReferrals.clear();
451 if (referralsp) {
452 char **tmp = referralsp;
453 while (*tmp) {
454 mReferrals.append(QByteArray(*tmp));
455 ldap_memfree(*tmp);
456 tmp++;
457 }
458 ldap_memfree((char *)referralsp);
459 }
460 mMatchedDn.clear();
461 if (matcheddn) {
462 mMatchedDn = QString::fromUtf8(matcheddn);
463 ldap_memfree(matcheddn);
464 }
465 if (errmsg) {
466 ldap_memfree(errmsg);
467 }
468 }
469 }
470
471 ldap_msgfree(msg);
472
473 return rescode;
474}
475
476static void addModOp(LDAPMod ***pmods, int mod_type, const QString &attr, const QByteArray *value = nullptr)
477{
478 // qCDebug(LDAP_CORE_LOG) << "type:" << mod_type << "attr:" << attr <<
479 // "value:" << QString::fromUtf8(value,value.size()) <<
480 // "size:" << value.size();
481 LDAPMod **mods;
482
483 mods = *pmods;
484
485 uint i = 0;
486
487 if (mods == nullptr) {
488 mods = (LDAPMod **)malloc(2 * sizeof(LDAPMod *));
489 mods[0] = (LDAPMod *)malloc(sizeof(LDAPMod));
490 mods[1] = nullptr;
491 memset(mods[0], 0, sizeof(LDAPMod));
492 } else {
493 while (mods[i] != nullptr && (strcmp(attr.toUtf8().constData(), mods[i]->mod_type) != 0 || (mods[i]->mod_op & ~LDAP_MOD_BVALUES) != mod_type)) {
494 i++;
495 }
496
497 if (mods[i] == nullptr) {
498 mods = (LDAPMod **)realloc(mods, (i + 2) * sizeof(LDAPMod *));
499 if (mods == nullptr) {
500 qCritical() << "addModOp: realloc";
501 return;
502 }
503 mods[i + 1] = nullptr;
504 mods[i] = (LDAPMod *)malloc(sizeof(LDAPMod));
505 memset(mods[i], 0, sizeof(LDAPMod));
506 }
507 }
508
509 mods[i]->mod_op = mod_type | LDAP_MOD_BVALUES;
510 if (mods[i]->mod_type == nullptr) {
511 mods[i]->mod_type = strdup(attr.toUtf8().constData());
512 }
513
514 *pmods = mods;
515
516 if (value == nullptr) {
517 return;
518 }
519
520 int vallen = value->size();
521 BerValue *berval;
522 berval = (BerValue *)malloc(sizeof(BerValue));
523 berval->bv_len = vallen;
524 if (vallen > 0) {
525 berval->bv_val = (char *)malloc(vallen);
526 memcpy(berval->bv_val, value->data(), vallen);
527 } else {
528 berval->bv_val = nullptr;
529 }
530
531 if (mods[i]->mod_vals.modv_bvals == nullptr) {
532 mods[i]->mod_vals.modv_bvals = (BerValue **)malloc(sizeof(BerValue *) * 2);
533 mods[i]->mod_vals.modv_bvals[0] = berval;
534 mods[i]->mod_vals.modv_bvals[1] = nullptr;
535 // qCDebug(LDAP_CORE_LOG) << "new bervalue struct" << attr << value;
536 } else {
537 uint j = 0;
538 while (mods[i]->mod_vals.modv_bvals[j] != nullptr) {
539 j++;
540 }
541 mods[i]->mod_vals.modv_bvals = (BerValue **)realloc(mods[i]->mod_vals.modv_bvals, (j + 2) * sizeof(BerValue *));
542 if (mods[i]->mod_vals.modv_bvals == nullptr) {
543 qCritical() << "addModOp: realloc";
544 free(berval);
545 return;
546 }
547 mods[i]->mod_vals.modv_bvals[j] = berval;
548 mods[i]->mod_vals.modv_bvals[j + 1] = nullptr;
549 qCDebug(LDAP_CORE_LOG) << j << ". new bervalue";
550 }
551}
552
553static void addControlOp(LDAPControl ***pctrls, const QString &oid, const QByteArray &value, bool critical)
554{
555 LDAPControl **ctrls;
556 auto ctrl = (LDAPControl *)malloc(sizeof(LDAPControl));
557
558 ctrls = *pctrls;
559
560 qCDebug(LDAP_CORE_LOG) << "oid:'" << oid << "' val: '" << value << "'";
561 int vallen = value.size();
562 ctrl->ldctl_value.bv_len = vallen;
563 if (vallen) {
564 ctrl->ldctl_value.bv_val = (char *)malloc(vallen);
565 memcpy(ctrl->ldctl_value.bv_val, value.data(), vallen);
566 } else {
567 ctrl->ldctl_value.bv_val = nullptr;
568 }
569 ctrl->ldctl_iscritical = critical;
570 ctrl->ldctl_oid = strdup(oid.toUtf8().constData());
571
572 uint i = 0;
573
574 if (ctrls == nullptr) {
575 ctrls = (LDAPControl **)malloc(2 * sizeof(LDAPControl *));
576 ctrls[0] = nullptr;
577 ctrls[1] = nullptr;
578 } else {
579 while (ctrls[i] != nullptr) {
580 i++;
581 }
582 ctrls[i + 1] = nullptr;
583 ctrls = (LDAPControl **)realloc(ctrls, (i + 2) * sizeof(LDAPControl *));
584 }
585 ctrls[i] = ctrl;
586 *pctrls = ctrls;
587}
588
589static void createControls(LDAPControl ***pctrls, const LdapControls &ctrls)
590{
591 for (int i = 0; i < ctrls.count(); ++i) {
592 addControlOp(pctrls, ctrls[i].oid(), ctrls[i].value(), ctrls[i].critical());
593 }
594}
595
596static void extractControls(LdapControls &ctrls, LDAPControl **pctrls)
597{
598 LdapControl control;
599 int i = 0;
600
601 while (pctrls[i]) {
602 LDAPControl *ctrl = pctrls[i];
603 control.setOid(QString::fromUtf8(ctrl->ldctl_oid));
604 control.setValue(QByteArray(ctrl->ldctl_value.bv_val, ctrl->ldctl_value.bv_len));
605 control.setCritical(ctrl->ldctl_iscritical);
606 ctrls.append(control);
607 i++;
608 }
609}
610
611int LdapOperation::bind(const QByteArray &creds, SASL_Callback_Proc *saslproc, void *data)
612{
613 return d->bind(creds, saslproc, data, true);
614}
615
616int LdapOperation::bind_s(SASL_Callback_Proc *saslproc, void *data)
617{
618 return d->bind(QByteArray(), saslproc, data, false);
619}
620
621int LdapOperation::search(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attributes)
622{
623 Q_ASSERT(d->mConnection);
624 LDAP *ld = (LDAP *)d->mConnection->handle();
625
626 char **attrs = nullptr;
627 int msgid;
628
629 LDAPControl **serverctrls = nullptr;
630 LDAPControl **clientctrls = nullptr;
631 createControls(&serverctrls, d->mServerCtrls);
632 createControls(&serverctrls, d->mClientCtrls);
633
634 int count = attributes.count();
635 if (count > 0) {
636 attrs = static_cast<char **>(malloc((count + 1) * sizeof(char *)));
637 for (int i = 0; i < count; i++) {
638 attrs[i] = strdup(attributes.at(i).toUtf8().constData());
639 }
640 attrs[count] = nullptr;
641 }
642
643 int lscope = LDAP_SCOPE_BASE;
644 switch (scope) {
645 case LdapUrl::Base:
646 lscope = LDAP_SCOPE_BASE;
647 break;
648 case LdapUrl::One:
649 lscope = LDAP_SCOPE_ONELEVEL;
650 break;
651 case LdapUrl::Sub:
652 lscope = LDAP_SCOPE_SUBTREE;
653 break;
654 }
655
656 qCDebug(LDAP_CORE_LOG) << "asyncSearch() base=\"" << base.toString() << "\" scope=" << (int)scope << "filter=\"" << filter << "\" attrs=" << attributes;
657 int retval = ldap_search_ext(ld,
658 base.toString().toUtf8().data(),
659 lscope,
660 filter.isEmpty() ? QByteArray("objectClass=*").data() : filter.toUtf8().data(),
661 attrs,
662 0,
663 serverctrls,
664 clientctrls,
665 nullptr,
666 d->mConnection->sizeLimit(),
667 &msgid);
668
669 ldap_controls_free(serverctrls);
670 ldap_controls_free(clientctrls);
671
672 // free the attributes list again
673 if (count > 0) {
674 for (int i = 0; i < count; i++) {
675 free(attrs[i]);
676 }
677 free(attrs);
678 }
679
680 if (retval == 0) {
681 retval = msgid;
682 }
683 return retval;
684}
685
686int LdapOperation::add(const LdapObject &object)
687{
688 Q_ASSERT(d->mConnection);
689 LDAP *ld = (LDAP *)d->mConnection->handle();
690
691 int msgid;
692 LDAPMod **lmod = nullptr;
693
694 LDAPControl **serverctrls = nullptr;
695 LDAPControl **clientctrls = nullptr;
696 createControls(&serverctrls, d->mServerCtrls);
697 createControls(&serverctrls, d->mClientCtrls);
698
699 for (LdapAttrMap::ConstIterator it = object.attributes().begin(); it != object.attributes().end(); ++it) {
700 QString attr = it.key();
701 for (LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2) {
702 addModOp(&lmod, 0, attr, &(*it2));
703 }
704 }
705
706 int retval = ldap_add_ext(ld, object.dn().toString().toUtf8().data(), lmod, serverctrls, clientctrls, &msgid);
707
708 ldap_controls_free(serverctrls);
709 ldap_controls_free(clientctrls);
710 ldap_mods_free(lmod, 1);
711 if (retval == 0) {
712 retval = msgid;
713 }
714 return retval;
715}
716
717int LdapOperation::add_s(const LdapObject &object)
718{
719 Q_ASSERT(d->mConnection);
720 LDAP *ld = (LDAP *)d->mConnection->handle();
721
722 LDAPMod **lmod = nullptr;
723
724 LDAPControl **serverctrls = nullptr;
725 LDAPControl **clientctrls = nullptr;
726 createControls(&serverctrls, d->mServerCtrls);
727 createControls(&serverctrls, d->mClientCtrls);
728
729 for (LdapAttrMap::ConstIterator it = object.attributes().begin(); it != object.attributes().end(); ++it) {
730 QString attr = it.key();
731 for (LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2) {
732 addModOp(&lmod, 0, attr, &(*it2));
733 }
734 }
735
736 int retval = ldap_add_ext_s(ld, object.dn().toString().toUtf8().data(), lmod, serverctrls, clientctrls);
737
738 ldap_controls_free(serverctrls);
739 ldap_controls_free(clientctrls);
740 ldap_mods_free(lmod, 1);
741 return retval;
742}
743
744int LdapOperation::add(const LdapDN &dn, const ModOps &ops)
745{
746 Q_ASSERT(d->mConnection);
747 LDAP *ld = (LDAP *)d->mConnection->handle();
748
749 int msgid;
750 LDAPMod **lmod = nullptr;
751
752 LDAPControl **serverctrls = nullptr;
753 LDAPControl **clientctrls = nullptr;
754 createControls(&serverctrls, d->mServerCtrls);
755 createControls(&serverctrls, d->mClientCtrls);
756
757 for (int i = 0; i < ops.count(); ++i) {
758 for (int j = 0; j < ops[i].values.count(); ++j) {
759 addModOp(&lmod, 0, ops[i].attr, &ops[i].values[j]);
760 }
761 }
762
763 int retval = ldap_add_ext(ld, dn.toString().toUtf8().data(), lmod, serverctrls, clientctrls, &msgid);
764
765 ldap_controls_free(serverctrls);
766 ldap_controls_free(clientctrls);
767 ldap_mods_free(lmod, 1);
768 if (retval == 0) {
769 retval = msgid;
770 }
771 return retval;
772}
773
774int LdapOperation::add_s(const LdapDN &dn, const ModOps &ops)
775{
776 Q_ASSERT(d->mConnection);
777 LDAP *ld = (LDAP *)d->mConnection->handle();
778
779 LDAPMod **lmod = nullptr;
780
781 LDAPControl **serverctrls = nullptr;
782 LDAPControl **clientctrls = nullptr;
783 createControls(&serverctrls, d->mServerCtrls);
784 createControls(&serverctrls, d->mClientCtrls);
785
786 for (int i = 0; i < ops.count(); ++i) {
787 for (int j = 0; j < ops[i].values.count(); ++j) {
788 addModOp(&lmod, 0, ops[i].attr, &ops[i].values[j]);
789 }
790 }
791 qCDebug(LDAP_CORE_LOG) << dn.toString();
792 int retval = ldap_add_ext_s(ld, dn.toString().toUtf8().data(), lmod, serverctrls, clientctrls);
793
794 ldap_controls_free(serverctrls);
795 ldap_controls_free(clientctrls);
796 ldap_mods_free(lmod, 1);
797 return retval;
798}
799
800int LdapOperation::rename(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold)
801{
802 Q_ASSERT(d->mConnection);
803 LDAP *ld = (LDAP *)d->mConnection->handle();
804
805 int msgid;
806
807 LDAPControl **serverctrls = nullptr;
808 LDAPControl **clientctrls = nullptr;
809 createControls(&serverctrls, d->mServerCtrls);
810 createControls(&serverctrls, d->mClientCtrls);
811
812 int retval = ldap_rename(ld,
813 dn.toString().toUtf8().data(),
814 newRdn.toUtf8().data(),
815 newSuperior.isEmpty() ? (char *)nullptr : newSuperior.toUtf8().data(),
816 deleteold,
817 serverctrls,
818 clientctrls,
819 &msgid);
820
821 ldap_controls_free(serverctrls);
822 ldap_controls_free(clientctrls);
823
824 if (retval == 0) {
825 retval = msgid;
826 }
827 return retval;
828}
829
830int LdapOperation::rename_s(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold)
831{
832 Q_ASSERT(d->mConnection);
833 LDAP *ld = (LDAP *)d->mConnection->handle();
834
835 LDAPControl **serverctrls = nullptr;
836 LDAPControl **clientctrls = nullptr;
837 createControls(&serverctrls, d->mServerCtrls);
838 createControls(&serverctrls, d->mClientCtrls);
839
840 int retval = ldap_rename_s(ld,
841 dn.toString().toUtf8().data(),
842 newRdn.toUtf8().data(),
843 newSuperior.isEmpty() ? (char *)nullptr : newSuperior.toUtf8().data(),
844 deleteold,
845 serverctrls,
846 clientctrls);
847
848 ldap_controls_free(serverctrls);
849 ldap_controls_free(clientctrls);
850
851 return retval;
852}
853
854int LdapOperation::del(const LdapDN &dn)
855{
856 Q_ASSERT(d->mConnection);
857 LDAP *ld = (LDAP *)d->mConnection->handle();
858
859 int msgid;
860
861 LDAPControl **serverctrls = nullptr;
862 LDAPControl **clientctrls = nullptr;
863 createControls(&serverctrls, d->mServerCtrls);
864 createControls(&serverctrls, d->mClientCtrls);
865
866 int retval = ldap_delete_ext(ld, dn.toString().toUtf8().data(), serverctrls, clientctrls, &msgid);
867
868 ldap_controls_free(serverctrls);
869 ldap_controls_free(clientctrls);
870
871 if (retval == 0) {
872 retval = msgid;
873 }
874 return retval;
875}
876
877int LdapOperation::del_s(const LdapDN &dn)
878{
879 Q_ASSERT(d->mConnection);
880 LDAP *ld = (LDAP *)d->mConnection->handle();
881
882 LDAPControl **serverctrls = nullptr;
883 LDAPControl **clientctrls = nullptr;
884 createControls(&serverctrls, d->mServerCtrls);
885 createControls(&serverctrls, d->mClientCtrls);
886
887 int retval = ldap_delete_ext_s(ld, dn.toString().toUtf8().data(), serverctrls, clientctrls);
888
889 ldap_controls_free(serverctrls);
890 ldap_controls_free(clientctrls);
891
892 return retval;
893}
894
895int LdapOperation::modify(const LdapDN &dn, const ModOps &ops)
896{
897 Q_ASSERT(d->mConnection);
898 LDAP *ld = (LDAP *)d->mConnection->handle();
899
900 int msgid;
901 LDAPMod **lmod = nullptr;
902
903 LDAPControl **serverctrls = nullptr;
904 LDAPControl **clientctrls = nullptr;
905 createControls(&serverctrls, d->mServerCtrls);
906 createControls(&serverctrls, d->mClientCtrls);
907
908 for (int i = 0; i < ops.count(); ++i) {
909 int mtype = 0;
910 switch (ops[i].type) {
911 case Mod_None:
912 mtype = 0;
913 break;
914 case Mod_Add:
915 mtype = LDAP_MOD_ADD;
916 break;
917 case Mod_Replace:
918 mtype = LDAP_MOD_REPLACE;
919 break;
920 case Mod_Del:
921 mtype = LDAP_MOD_DELETE;
922 break;
923 }
924 addModOp(&lmod, mtype, ops[i].attr, nullptr);
925 for (int j = 0; j < ops[i].values.count(); ++j) {
926 addModOp(&lmod, mtype, ops[i].attr, &ops[i].values[j]);
927 }
928 }
929
930 int retval = ldap_modify_ext(ld, dn.toString().toUtf8().data(), lmod, serverctrls, clientctrls, &msgid);
931
932 ldap_controls_free(serverctrls);
933 ldap_controls_free(clientctrls);
934 ldap_mods_free(lmod, 1);
935 if (retval == 0) {
936 retval = msgid;
937 }
938 return retval;
939}
940
941int LdapOperation::modify_s(const LdapDN &dn, const ModOps &ops)
942{
943 Q_ASSERT(d->mConnection);
944 LDAP *ld = (LDAP *)d->mConnection->handle();
945
946 LDAPMod **lmod = nullptr;
947
948 LDAPControl **serverctrls = nullptr;
949 LDAPControl **clientctrls = nullptr;
950 createControls(&serverctrls, d->mServerCtrls);
951 createControls(&serverctrls, d->mClientCtrls);
952
953 for (int i = 0; i < ops.count(); ++i) {
954 int mtype = 0;
955 switch (ops[i].type) {
956 case Mod_None:
957 mtype = 0;
958 break;
959 case Mod_Add:
960 mtype = LDAP_MOD_ADD;
961 break;
962 case Mod_Replace:
963 mtype = LDAP_MOD_REPLACE;
964 break;
965 case Mod_Del:
966 mtype = LDAP_MOD_DELETE;
967 break;
968 }
969 addModOp(&lmod, mtype, ops[i].attr, nullptr);
970 for (int j = 0; j < ops[i].values.count(); ++j) {
971 addModOp(&lmod, mtype, ops[i].attr, &ops[i].values[j]);
972 }
973 }
974
975 int retval = ldap_modify_ext_s(ld, dn.toString().toUtf8().data(), lmod, serverctrls, clientctrls);
976
977 ldap_controls_free(serverctrls);
978 ldap_controls_free(clientctrls);
979 ldap_mods_free(lmod, 1);
980 return retval;
981}
982
983int LdapOperation::compare(const LdapDN &dn, const QString &attr, const QByteArray &value)
984{
985 Q_ASSERT(d->mConnection);
986 LDAP *ld = (LDAP *)d->mConnection->handle();
987 int msgid;
988
989 LDAPControl **serverctrls = nullptr;
990 LDAPControl **clientctrls = nullptr;
991 createControls(&serverctrls, d->mServerCtrls);
992 createControls(&serverctrls, d->mClientCtrls);
993
994 int vallen = value.size();
995 BerValue *berval;
996 berval = (BerValue *)malloc(sizeof(BerValue));
997 berval->bv_val = (char *)malloc(vallen);
998 berval->bv_len = vallen;
999 memcpy(berval->bv_val, value.data(), vallen);
1000
1001 int retval = ldap_compare_ext(ld, dn.toString().toUtf8().data(), attr.toUtf8().data(), berval, serverctrls, clientctrls, &msgid);
1002
1003 ber_bvfree(berval);
1004 ldap_controls_free(serverctrls);
1005 ldap_controls_free(clientctrls);
1006
1007 if (retval == 0) {
1008 retval = msgid;
1009 }
1010 return retval;
1011}
1012
1013int LdapOperation::compare_s(const LdapDN &dn, const QString &attr, const QByteArray &value)
1014{
1015 Q_ASSERT(d->mConnection);
1016 LDAP *ld = (LDAP *)d->mConnection->handle();
1017
1018 LDAPControl **serverctrls = nullptr;
1019 LDAPControl **clientctrls = nullptr;
1020 createControls(&serverctrls, d->mServerCtrls);
1021 createControls(&serverctrls, d->mClientCtrls);
1022
1023 int vallen = value.size();
1024 BerValue *berval;
1025 berval = (BerValue *)malloc(sizeof(BerValue));
1026 berval->bv_val = (char *)malloc(vallen);
1027 berval->bv_len = vallen;
1028 memcpy(berval->bv_val, value.data(), vallen);
1029
1030 int retval = ldap_compare_ext_s(ld, dn.toString().toUtf8().data(), attr.toUtf8().data(), berval, serverctrls, clientctrls);
1031
1032 ber_bvfree(berval);
1033 ldap_controls_free(serverctrls);
1034 ldap_controls_free(clientctrls);
1035
1036 return retval;
1037}
1038
1039int LdapOperation::exop(const QString &oid, const QByteArray &data)
1040{
1041 Q_ASSERT(d->mConnection);
1042#if HAVE_LDAP_EXTENDED_OPERATION
1043 LDAP *ld = (LDAP *)d->mConnection->handle();
1044 int msgid;
1045
1046 LDAPControl **serverctrls = nullptr;
1047 LDAPControl **clientctrls = nullptr;
1048 createControls(&serverctrls, d->mServerCtrls);
1049 createControls(&serverctrls, d->mClientCtrls);
1050
1051 int vallen = data.size();
1052 BerValue *berval;
1053 berval = (BerValue *)malloc(sizeof(BerValue));
1054 berval->bv_val = (char *)malloc(vallen);
1055 berval->bv_len = vallen;
1056 memcpy(berval->bv_val, data.data(), vallen);
1057
1058 int retval = ldap_extended_operation(ld, oid.toUtf8().data(), berval, serverctrls, clientctrls, &msgid);
1059
1060 ber_bvfree(berval);
1061 ldap_controls_free(serverctrls);
1062 ldap_controls_free(clientctrls);
1063
1064 if (retval == 0) {
1065 retval = msgid;
1066 }
1067 return retval;
1068#else
1069 qCritical() << "Your LDAP client libraries don't support extended operations.";
1070 return -1;
1071#endif
1072}
1073
1074int LdapOperation::exop_s(const QString &oid, const QByteArray &data)
1075{
1076#if HAVE_LDAP_EXTENDED_OPERATION_S
1077 Q_ASSERT(d->mConnection);
1078 LDAP *ld = (LDAP *)d->mConnection->handle();
1079 BerValue *retdata;
1080 char *retoid;
1081
1082 LDAPControl **serverctrls = nullptr;
1083 LDAPControl **clientctrls = nullptr;
1084 createControls(&serverctrls, d->mServerCtrls);
1085 createControls(&serverctrls, d->mClientCtrls);
1086
1087 int vallen = data.size();
1088 BerValue *berval;
1089 berval = (BerValue *)malloc(sizeof(BerValue));
1090 berval->bv_val = (char *)malloc(vallen);
1091 berval->bv_len = vallen;
1092 memcpy(berval->bv_val, data.data(), vallen);
1093
1094 int retval = ldap_extended_operation_s(ld, oid.toUtf8().data(), berval, serverctrls, clientctrls, &retoid, &retdata);
1095
1096 ber_bvfree(berval);
1097 ber_bvfree(retdata);
1098 free(retoid);
1099 ldap_controls_free(serverctrls);
1100 ldap_controls_free(clientctrls);
1101
1102 return retval;
1103#else
1104 qCritical() << "Your LDAP client libraries don't support extended operations.";
1105 return -1;
1106#endif
1107}
1108
1109int LdapOperation::abandon(int id)
1110{
1111 Q_ASSERT(d->mConnection);
1112 LDAP *ld = (LDAP *)d->mConnection->handle();
1113
1114 LDAPControl **serverctrls = nullptr;
1115 LDAPControl **clientctrls = nullptr;
1116 createControls(&serverctrls, d->mServerCtrls);
1117 createControls(&serverctrls, d->mClientCtrls);
1118
1119 int retval = ldap_abandon_ext(ld, id, serverctrls, clientctrls);
1120
1121 ldap_controls_free(serverctrls);
1122 ldap_controls_free(clientctrls);
1123
1124 return retval;
1125}
1126
1127int LdapOperation::waitForResult(int id, int msecs)
1128{
1129 Q_ASSERT(d->mConnection);
1130 LDAP *ld = (LDAP *)d->mConnection->handle();
1131
1132 LDAPMessage *msg;
1133
1134 QElapsedTimer stopWatch;
1135 stopWatch.start();
1136 int attempt(1);
1137 int timeout(0);
1138
1139 do {
1140 // Calculate the timeout value to use and assign it to a timeval structure
1141 // see man select (2) for details
1142 timeout = kldap_timeout_value(msecs, stopWatch.elapsed());
1143 qCDebug(LDAP_CORE_LOG) << "(" << id << "," << msecs << "): Waiting" << timeout << "msecs for result. Attempt #" << attempt++;
1144 struct timeval tv;
1145 tv.tv_sec = timeout / 1000;
1146 tv.tv_usec = (timeout % 1000) * 1000;
1147
1148 // Wait for a result
1149 int rescode = ldap_result(ld, id, 0, timeout < 0 ? nullptr : &tv, &msg);
1150 if (rescode == -1) {
1151 return -1;
1152 }
1153 // Act on the return code
1154 if (rescode != 0) {
1155 // Some kind of result is available for processing
1156 return d->processResult(rescode, msg);
1157 }
1158 } while (msecs == -1 || stopWatch.elapsed() < msecs);
1159
1160 return 0; // timeout
1161}
1162
1163#else
1164
1165int LdapOperation::bind(const QByteArray &creds, SASL_Callback_Proc *saslproc, void *data)
1166{
1167 qCritical() << "LDAP support not compiled";
1168 return -1;
1169}
1170
1171int LdapOperation::bind_s(SASL_Callback_Proc *saslproc, void *data)
1172{
1173 qCritical() << "LDAP support not compiled";
1174 return -1;
1175}
1176
1177int LdapOperation::search(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attributes)
1178{
1179 qCritical() << "LDAP support not compiled";
1180 return -1;
1181}
1182
1184{
1185 qCritical() << "LDAP support not compiled";
1186 return -1;
1187}
1188
1190{
1191 qCritical() << "LDAP support not compiled";
1192 return -1;
1193}
1194
1195int LdapOperation::add(const LdapDN &dn, const ModOps &ops)
1196{
1197 qCritical() << "LDAP support not compiled";
1198 return -1;
1199}
1200
1201int LdapOperation::add_s(const LdapDN &dn, const ModOps &ops)
1202{
1203 qCritical() << "LDAP support not compiled";
1204 return -1;
1205}
1206
1207int LdapOperation::rename(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold)
1208{
1209 qCritical() << "LDAP support not compiled";
1210 return -1;
1211}
1212
1213int LdapOperation::rename_s(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold)
1214{
1215 qCritical() << "LDAP support not compiled";
1216 return -1;
1217}
1218
1219int LdapOperation::del(const LdapDN &dn)
1220{
1221 qCritical() << "LDAP support not compiled";
1222 return -1;
1223}
1224
1225int LdapOperation::del_s(const LdapDN &dn)
1226{
1227 qCritical() << "LDAP support not compiled";
1228 return -1;
1229}
1230
1231int LdapOperation::modify(const LdapDN &dn, const ModOps &ops)
1232{
1233 qCritical() << "LDAP support not compiled";
1234 return -1;
1235}
1236
1237int LdapOperation::modify_s(const LdapDN &dn, const ModOps &ops)
1238{
1239 qCritical() << "LDAP support not compiled";
1240 return -1;
1241}
1242
1243int LdapOperation::compare(const LdapDN &dn, const QString &attr, const QByteArray &value)
1244{
1245 qCritical() << "LDAP support not compiled";
1246 return -1;
1247}
1248
1249int LdapOperation::exop(const QString &oid, const QByteArray &data)
1250{
1251 qCritical() << "LDAP support not compiled";
1252 return -1;
1253}
1254
1255int LdapOperation::compare_s(const LdapDN &dn, const QString &attr, const QByteArray &value)
1256{
1257 qCritical() << "LDAP support not compiled";
1258 return -1;
1259}
1260
1261int LdapOperation::exop_s(const QString &oid, const QByteArray &data)
1262{
1263 qCritical() << "LDAP support not compiled";
1264 return -1;
1265}
1266
1267int LdapOperation::waitForResult(int id, int msecs)
1268{
1269 qCritical() << "LDAP support not compiled";
1270 return -1;
1271}
1272
1274{
1275 qCritical() << "LDAP support not compiled";
1276 return -1;
1277}
1278
1279#endif
This class represents a connection to an LDAP server.
const LdapServer & server() const
Returns the connection parameters which was specified with an LDAP Url or a LdapServer structure.
void * saslHandle() const
Returns the opaqe sasl-library specific SASL object.
void * handle() const
Returns the opaqe client-library specific LDAP object.
This class represents an LDAP Control.
Definition ldapcontrol.h:29
void setValue(const QByteArray &value)
Sets the control's value.
void setOid(const QString &oid)
Sets the control's OID.
void setCritical(bool critical)
Sets the control's criticality.
This class represents an LDAP Object.
Definition ldapobject.h:31
This class allows sending an ldap operation (search, rename, modify, delete, compare,...
void setClientControls(const LdapControls &ctrls)
Sets the client controls which will sent with each operation.
int bind_s(SASL_Callback_Proc *saslproc=nullptr, void *data=nullptr)
Binds to the server which specified in the connection object.
QByteArray extendedOid() const
Returns the OID of the extended operation response (result returned RES_EXTENDED).
QString matchedDn() const
The server might supply a matched DN string in the message indicating how much of a name in a request...
QList< QByteArray > referrals() const
This function returns the referral strings from the parsed message (if any).
int abandon(int id)
Abandons a long-running operation.
LdapControls clientControls() const
Returns the client controls (which set by setClientControls()).
QByteArray serverCred() const
Returns the server response for a bind request (result returned RES_BIND).
QByteArray extendedData() const
Returns the data from the extended operation response (result returned RES_EXTENDED).
int modify(const LdapDN &dn, const ModOps &ops)
Starts a modify operation on the given DN.
int exop(const QString &oid, const QByteArray &data)
Starts an extended operation specified with oid and data.
int rename(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold=true)
Starts a modrdn operation on given DN, changing its RDN to newRdn, changing its parent to newSuperior...
int rename_s(const LdapDN &dn, const QString &newRdn, const QString &newSuperior, bool deleteold=true)
Performs a modrdn operation on given DN, changing its RDN to newRdn, changing its parent to newSuperi...
void setConnection(LdapConnection &conn)
Sets the connection object.
void setServerControls(const LdapControls &ctrls)
Sets the server controls which will sent with each operation.
int add(const LdapObject &object)
Starts an addition operation.
int compare(const LdapDN &dn, const QString &attr, const QByteArray &value)
Starts a compare operation on the given DN, compares the specified attribute with the given value.
LdapObject object() const
Returns the result object if result() returned RES_SEARCH_ENTRY.
int compare_s(const LdapDN &dn, const QString &attr, const QByteArray &value)
Performs a compare operation on the given DN, compares the specified attribute with the given value.
int del_s(const LdapDN &dn)
Deletes the given DN.
int bind(const QByteArray &creds=QByteArray(), SASL_Callback_Proc *saslproc=nullptr, void *data=nullptr)
Binds to the server which specified in the connection object.
LdapConnection & connection()
Returns the connection object.
int modify_s(const LdapDN &dn, const ModOps &ops)
Performs a modify operation on the given DN.
int del(const LdapDN &dn)
Starts a delete operation on the given DN.
LdapControls controls() const
Returns the server controls from the returned ldap message (grabbed by result()).
LdapControls serverControls() const
Returns the server controls (which set by setServerControls()).
int exop_s(const QString &oid, const QByteArray &data)
Performs an extended operation specified with oid and data.
int waitForResult(int id, int msecs=-1)
Waits for up to msecs milliseconds for a result message from the LDAP server.
int add_s(const LdapObject &object)
Adds the specified object to the LDAP database.
int search(const LdapDN &base, LdapUrl::Scope scope, const QString &filter, const QStringList &attrs)
Starts a search operation with the given base DN, scope, filter and result attributes.
A class that contains LDAP server connection settings.
Definition ldapserver.h:27
QString realm() const
Returns the realm of the LDAP connection.
QString password() const
Returns the password of the LDAP connection.
QString bindDn() const
Returns the bindDn of the LDAP connection.
QString user() const
Returns the user of the LDAP connection.
Auth auth() const
Returns the authentication method of the LDAP connection.
QString mech() const
Returns the mech of the LDAP connection.
enum { Base, One, Sub } Scope
Describes the scope of the LDAP url.
Definition ldapurl.h:44
char * toString(const EngineQuery &query)
QString name(StandardAction id)
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
const char * constData() const const
char * data()
bool isEmpty() const const
qsizetype size() const const
qint64 elapsed() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
qsizetype count() const const
ConstIterator
void clear()
QChar * data()
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:07 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.