4 Copyright (C) Simo Sorce 2005-2008
5 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
7 ** NOTE! The following LGPL license applies to the ldb
8 ** library. This does NOT imply that all of Samba is released
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 3 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public
22 License along with this library; if not, see <http://www.gnu.org/licenses/>.
28 * Component: ldb paged results control module
30 * Description: this module caches a complete search and sends back
31 * results in chunks as asked by the client
33 * Author: Garming Sam and Aaron Haslett
35 * Note: Based on the original paged_results.c by Simo Sorce and
36 * vlv_pagination.c by Douglas Bagnall and Garming Sam.
40 #include "auth/auth.h"
42 #include "dsdb/samdb/samdb.h"
43 #include "libcli/security/security.h"
44 #include "libcli/ldap/ldap_errors.h"
46 #include "system/filesys.h"
47 #include "system/time.h"
48 #include "ldb_module.h"
49 #include "dsdb/samdb/samdb.h"
51 #include "dsdb/common/util.h"
52 #include "lib/util/dlinklist.h"
54 /* Referrals are temporarily stored in a linked list */
55 struct referral_store
{
57 struct referral_store
*next
;
62 struct results_store
{
63 struct results_store
*prev
, *next
;
65 struct private_data
*priv
;
70 struct referral_store
*first_ref
;
71 struct referral_store
*last_ref
;
73 struct ldb_control
**controls
;
78 size_t result_array_size
;
80 struct ldb_control
**down_controls
;
81 const char * const *attrs
;
84 struct ldb_parse_tree
*expr
;
89 uint32_t next_free_id
;
91 struct results_store
*store
;
94 static int store_destructor(struct results_store
*del
)
96 struct private_data
*priv
= del
->priv
;
97 DLIST_REMOVE(priv
->store
, del
);
99 priv
->num_stores
-= 1;
104 static struct results_store
*new_store(struct private_data
*priv
)
106 struct results_store
*newr
;
107 uint32_t new_id
= priv
->next_free_id
++;
109 /* TODO: we should have a limit on the number of
110 * outstanding paged searches
113 newr
= talloc_zero(priv
, struct results_store
);
114 if (!newr
) return NULL
;
118 newr
->cookie
= talloc_asprintf(newr
, "%d", new_id
);
124 newr
->timestamp
= time(NULL
);
126 DLIST_ADD(priv
->store
, newr
);
128 priv
->num_stores
+= 1;
130 talloc_set_destructor(newr
, store_destructor
);
132 if (priv
->num_stores
> 10) {
133 struct results_store
*last
;
135 * 10 is the default for MaxResultSetsPerConn --
136 * possibly need to parameterize it.
138 last
= DLIST_TAIL(priv
->store
);
145 struct paged_context
{
146 struct ldb_module
*module
;
147 struct ldb_request
*req
;
149 struct results_store
*store
;
151 struct ldb_control
**controls
;
154 static int send_referrals(struct results_store
*store
,
155 struct ldb_request
*req
)
158 struct referral_store
*node
;
159 while (store
->first_ref
!= NULL
) {
160 node
= store
->first_ref
;
161 ret
= ldb_module_send_referral(req
, node
->ref
);
162 if (ret
!= LDB_SUCCESS
) {
165 store
->first_ref
= node
->next
;
171 /* Start an ldb request for a single object by GUID */
172 static int paged_search_by_dn_guid(struct ldb_module
*module
,
173 struct paged_context
*ac
,
174 struct ldb_result
**result
,
175 const struct GUID
*guid
,
176 const char * const *attrs
,
177 struct ldb_parse_tree
*expr
)
180 struct ldb_request
*req
;
181 struct ldb_result
*res
;
183 struct GUID_txt_buf guid_str
;
185 /* Use controls passed in on the downreq */
186 struct ldb_control
**controls
= ac
->store
->down_controls
;
188 struct ldb_context
*ldb
= ldb_module_get_ctx(module
);
190 dn
= ldb_dn_new_fmt(ac
, ldb
, "<GUID=%s>",
191 GUID_buf_string(guid
, &guid_str
));
196 res
= talloc_zero(ac
, struct ldb_result
);
202 ret
= ldb_build_search_req_ex(&req
, ldb
, ac
,
209 ldb_search_default_callback
,
211 if (ret
!= LDB_SUCCESS
) {
218 * Ensure the dn lasts only as long as the request,
219 * as we will have a lot of these (one per object
223 talloc_steal(req
, dn
);
225 ret
= ldb_request(ldb
, req
);
226 if (ret
== LDB_SUCCESS
) {
227 ret
= ldb_wait(req
->handle
, LDB_WAIT_ALL
);
231 if (ret
!= LDB_SUCCESS
) {
240 static int paged_results(struct paged_context
*ac
)
242 struct ldb_paged_control
*paged
;
243 unsigned int i
, num_ctrls
;
246 if (ac
->store
== NULL
) {
247 return LDB_ERR_OPERATIONS_ERROR
;
250 while (ac
->store
->last_i
< ac
->store
->num_entries
&& ac
->size
> 0) {
251 struct GUID
*guid
= &ac
->store
->results
[ac
->store
->last_i
++];
252 struct ldb_result
*result
= NULL
;
257 * Note: In the case that an object has been moved to a
258 * different place in the LDAP tree, we might expect the object
259 * to disappear from paged results. If we were going to
260 * implement that behaviour, we would do it here by passing
261 * down the original container DN to the search.
262 * However, testing shows that, on Windows, the moved object
263 * remains in the paged results. So, we are matching Windows
264 * behaviour here by leaving out the scope.
266 ret
= paged_search_by_dn_guid(ac
->module
, ac
, &result
, guid
,
267 ac
->req
->op
.search
.attrs
,
269 if (ret
== LDAP_NO_SUCH_OBJECT
||
270 (ret
== LDB_SUCCESS
&& result
->count
== 0)) {
271 /* The thing isn't there TODO, which we quietly
272 ignore and go on to send an extra one
275 } else if (ret
!= LDB_SUCCESS
) {
279 ret
= ldb_module_send_entry(ac
->req
, result
->msgs
[0],
281 if (ret
!= LDB_SUCCESS
) {
286 if (ac
->store
->first_ref
) {
287 /* There is no right place to put references in the sorted
288 results, so we send them as soon as possible.
290 ret
= send_referrals(ac
->store
, ac
->req
);
291 if (ret
!= LDB_SUCCESS
) {
296 /* return result done */
300 if (ac
->store
->controls
!= NULL
) {
301 while (ac
->store
->controls
[i
]) i
++; /* counting */
306 ac
->controls
= talloc_array(ac
, struct ldb_control
*, num_ctrls
+1);
307 if (ac
->controls
== NULL
) {
308 return LDB_ERR_OPERATIONS_ERROR
;
310 ac
->controls
[num_ctrls
] = NULL
;
312 for (i
= 0; i
< (num_ctrls
-1); i
++) {
313 ac
->controls
[i
] = talloc_reference(ac
->controls
,
314 ac
->store
->controls
[i
]);
317 ac
->controls
[i
] = talloc(ac
->controls
, struct ldb_control
);
318 if (ac
->controls
[i
] == NULL
) {
319 return LDB_ERR_OPERATIONS_ERROR
;
322 ac
->controls
[i
]->oid
= talloc_strdup(ac
->controls
[i
],
323 LDB_CONTROL_PAGED_RESULTS_OID
);
324 if (ac
->controls
[i
]->oid
== NULL
) {
325 return LDB_ERR_OPERATIONS_ERROR
;
328 ac
->controls
[i
]->critical
= 0;
330 paged
= talloc(ac
->controls
[i
], struct ldb_paged_control
);
332 return LDB_ERR_OPERATIONS_ERROR
;
335 ac
->controls
[i
]->data
= paged
;
339 paged
->cookie
= NULL
;
340 paged
->cookie_len
= 0;
342 paged
->size
= ac
->store
->num_entries
;
343 paged
->cookie
= talloc_strdup(paged
, ac
->store
->cookie
);
344 paged
->cookie_len
= strlen(paged
->cookie
) + 1;
350 static int save_referral(struct results_store
*store
, char *ref
)
352 struct referral_store
*node
= talloc(store
,
353 struct referral_store
);
355 return LDB_ERR_OPERATIONS_ERROR
;
358 node
->ref
= talloc_steal(node
, ref
);
360 if (store
->first_ref
== NULL
) {
361 store
->first_ref
= node
;
363 store
->last_ref
->next
= node
;
365 store
->last_ref
= node
;
369 static int paged_search_callback(struct ldb_request
*req
,
370 struct ldb_reply
*ares
)
372 struct paged_context
*ac
;
373 struct results_store
*store
;
375 const struct ldb_val
*guid_blob
;
379 ac
= talloc_get_type(req
->context
, struct paged_context
);
383 return ldb_module_done(ac
->req
, NULL
, NULL
,
384 LDB_ERR_OPERATIONS_ERROR
);
386 if (ares
->error
!= LDB_SUCCESS
) {
387 return ldb_module_done(ac
->req
, ares
->controls
,
388 ares
->response
, ares
->error
);
391 switch (ares
->type
) {
392 case LDB_REPLY_ENTRY
:
393 if (store
->results
== NULL
) {
394 store
->num_entries
= 0;
395 store
->result_array_size
= 16;
396 store
->results
= talloc_array(store
, struct GUID
,
397 store
->result_array_size
);
398 if (store
->results
== NULL
) {
399 return ldb_module_done(ac
->req
, NULL
, NULL
,
400 LDB_ERR_OPERATIONS_ERROR
);
402 } else if (store
->num_entries
== store
->result_array_size
) {
403 if (store
->result_array_size
> INT_MAX
/2) {
404 return ldb_module_done(ac
->req
, NULL
, NULL
,
405 LDB_ERR_OPERATIONS_ERROR
);
407 store
->result_array_size
*= 2;
408 store
->results
= talloc_realloc(store
, store
->results
,
410 store
->result_array_size
);
411 if (store
->results
== NULL
) {
412 return ldb_module_done(ac
->req
, NULL
, NULL
,
413 LDB_ERR_OPERATIONS_ERROR
);
417 guid_blob
= ldb_dn_get_extended_component(ares
->message
->dn
,
419 status
= GUID_from_ndr_blob(guid_blob
, &guid
);
420 if (!NT_STATUS_IS_OK(status
)) {
421 return ldb_module_done(ac
->req
, NULL
, NULL
,
422 LDB_ERR_OPERATIONS_ERROR
);
425 /* Redundant paranoid check */
426 if (store
->num_entries
> store
->result_array_size
) {
427 return ldb_module_done(ac
->req
, NULL
, NULL
,
428 LDB_ERR_OPERATIONS_ERROR
);
431 store
->results
[store
->num_entries
] = guid
;
432 store
->num_entries
++;
435 case LDB_REPLY_REFERRAL
:
436 ret
= save_referral(store
, ares
->referral
);
437 if (ret
!= LDB_SUCCESS
) {
438 return ldb_module_done(ac
->req
, NULL
, NULL
, ret
);
443 if (store
->num_entries
!= 0) {
444 store
->results
= talloc_realloc(store
, store
->results
,
447 if (store
->results
== NULL
) {
448 return ldb_module_done(ac
->req
, NULL
, NULL
,
449 LDB_ERR_OPERATIONS_ERROR
);
452 store
->result_array_size
= store
->num_entries
;
454 ac
->store
->controls
= talloc_move(ac
->store
, &ares
->controls
);
455 ret
= paged_results(ac
);
456 return ldb_module_done(ac
->req
, ac
->controls
,
457 ares
->response
, ret
);
463 static struct ldb_control
**
464 paged_results_copy_down_controls(TALLOC_CTX
*mem_ctx
,
465 struct ldb_control
**controls
)
468 struct ldb_control
**new_controls
;
469 unsigned int i
, j
, num_ctrls
;
470 if (controls
== NULL
) {
474 for (num_ctrls
= 0; controls
[num_ctrls
]; num_ctrls
++);
476 new_controls
= talloc_array(mem_ctx
, struct ldb_control
*, num_ctrls
);
477 if (new_controls
== NULL
) {
481 for (j
= 0, i
= 0; i
< (num_ctrls
); i
++) {
482 struct ldb_control
*control
= controls
[i
];
483 if (control
->oid
== NULL
) {
486 if (strcmp(control
->oid
, LDB_CONTROL_PAGED_RESULTS_OID
) == 0) {
490 * ASQ changes everything, do not copy it down for the
493 if (strcmp(control
->oid
, LDB_CONTROL_ASQ_OID
) == 0) {
496 new_controls
[j
] = talloc_steal(new_controls
, control
);
499 new_controls
[j
] = NULL
;
503 static const char * const *paged_copy_attrs(TALLOC_CTX
*mem_ctx
,
504 const char * const *attrs
) {
506 const char **new_attrs
;
510 new_attrs
= ldb_attr_list_copy(mem_ctx
, attrs
);
512 for (i
=0; attrs
[i
] != NULL
; i
++) {
513 new_attrs
[i
] = talloc_strdup(mem_ctx
, attrs
[i
]);
520 * Check if two sets of controls are the same except for the paged results
521 * control in the request controls. This function is messy because request
522 * control lists can contain controls that were NULL'd by the rootdse. We
523 * must ignore those entries. This function is not portable.
525 static bool paged_controls_same(struct ldb_request
*req
,
526 struct ldb_control
**down_controls
) {
528 unsigned int num_down_controls
, num_non_null_req_controls
;
529 struct ldb_control
*ctrl
;
531 num_down_controls
= 0;
532 for (i
=0; down_controls
[i
] != NULL
; i
++) {
535 ctrl
= ldb_request_get_control(req
, down_controls
[i
]->oid
);
541 num_non_null_req_controls
= 0;
542 for (i
=0; req
->controls
[i
] != NULL
; i
++) {
543 if (req
->controls
[i
]->oid
!= NULL
&&
544 strcmp(req
->controls
[i
]->oid
,
545 LDB_CONTROL_ASQ_OID
) != 0) {
546 num_non_null_req_controls
++;
550 /* At this point we have the number of non-null entries for both
551 * control lists and we know that:
552 * 1. down_controls does not contain the paged control or ASQ
553 * (because paged_results_copy_down_controls excludes it)
554 * 2. req->controls does contain the paged control
555 * (because this function is only called if this is true)
556 * 3. down_controls is a subset of non-null controls in req->controls
558 * So to confirm that the two lists are identical except for the paged
559 * control and possibly ASQ, all we need to check is: */
560 if (num_non_null_req_controls
== num_down_controls
+ 1) {
566 static bool paged_attrs_same(const char * const *attrs_1
,
567 const char * const *attrs_2
) {
569 if (attrs_1
== NULL
|| attrs_2
== NULL
) {
570 if (attrs_1
== NULL
&& attrs_2
== NULL
) {
576 for (i
=0; attrs_1
[i
] != NULL
; i
++) {
577 if (!ldb_attr_in_list(attrs_2
, attrs_1
[i
])) {
584 static int paged_search(struct ldb_module
*module
, struct ldb_request
*req
)
586 struct ldb_context
*ldb
;
587 struct ldb_control
*control
;
588 struct private_data
*private_data
;
589 struct ldb_paged_control
*paged_ctrl
;
590 struct ldb_request
*search_req
;
591 struct paged_context
*ac
;
594 ldb
= ldb_module_get_ctx(module
);
596 /* check if there's a paged request control */
597 control
= ldb_request_get_control(req
, LDB_CONTROL_PAGED_RESULTS_OID
);
598 if (control
== NULL
) {
599 /* not found go on */
600 return ldb_next_request(module
, req
);
603 paged_ctrl
= talloc_get_type(control
->data
, struct ldb_paged_control
);
605 return LDB_ERR_PROTOCOL_ERROR
;
608 private_data
= talloc_get_type(ldb_module_get_private(module
),
609 struct private_data
);
611 ac
= talloc_zero(req
, struct paged_context
);
613 ldb_set_errstring(ldb
, "Out of Memory");
614 return LDB_ERR_OPERATIONS_ERROR
;
619 ac
->size
= paged_ctrl
->size
;
622 * Apparently some clients send more than 2^31. This
623 * violates the ldap standard, but we need to cope.
624 * In the future, if maximum result sizes are implemented in
625 * Samba, we should also clamp the page size to the maximum
628 ac
->size
= 0x7FFFFFFF;
631 /* check if it is a continuation search the store */
632 if (paged_ctrl
->cookie_len
== 0) {
633 struct ldb_control
*ext_ctrl
;
634 struct ldb_control
**controls
;
635 static const char * const attrs
[1] = { NULL
};
637 if (paged_ctrl
->size
== 0) {
638 return LDB_ERR_OPERATIONS_ERROR
;
641 ac
->store
= new_store(private_data
);
642 if (ac
->store
== NULL
) {
643 return LDB_ERR_OPERATIONS_ERROR
;
646 controls
= req
->controls
;
647 ext_ctrl
= ldb_request_get_control(req
,
648 LDB_CONTROL_EXTENDED_DN_OID
);
649 if (ext_ctrl
== NULL
) {
651 * Add extended_dn control to the request if there
652 * isn't already one. We'll get the GUID out of it in
653 * the callback. This is a workaround for the case
654 * where ntsecuritydescriptor forbids fetching GUIDs
655 * for the current user.
657 struct ldb_request
*req_extended_dn
;
658 struct ldb_extended_dn_control
*ext_ctrl_data
;
659 req_extended_dn
= talloc_zero(req
, struct ldb_request
);
660 req_extended_dn
->controls
= req
->controls
;
661 ext_ctrl_data
= talloc_zero(req
,
662 struct ldb_extended_dn_control
);
663 ext_ctrl_data
->type
= 1;
665 ret
= ldb_request_add_control(req_extended_dn
,
666 LDB_CONTROL_EXTENDED_DN_OID
,
669 if (ret
!= LDB_SUCCESS
) {
672 controls
= req_extended_dn
->controls
;
675 ret
= ldb_build_search_req_ex(&search_req
, ldb
, ac
,
677 req
->op
.search
.scope
,
682 paged_search_callback
,
684 if (ret
!= LDB_SUCCESS
) {
688 ac
->store
->expr
= talloc_steal(ac
->store
, req
->op
.search
.tree
);
689 ac
->store
->expr_str
= ldb_filter_from_tree(ac
->store
,
690 req
->op
.search
.tree
);
691 ac
->store
->attrs
= paged_copy_attrs(ac
->store
,
692 req
->op
.search
.attrs
);
694 /* save it locally and remove it from the list */
695 /* we do not need to replace them later as we
696 * are keeping the original req intact */
697 if (!ldb_save_controls(control
, search_req
, NULL
)) {
698 return LDB_ERR_OPERATIONS_ERROR
;
700 ac
->store
->down_controls
=
701 paged_results_copy_down_controls(ac
->store
, req
->controls
);
702 if (ac
->store
->down_controls
== NULL
) {
703 return LDB_ERR_OPERATIONS_ERROR
;
706 return ldb_next_request(module
, search_req
);
709 struct results_store
*current
= NULL
;
713 /* TODO: age out old outstanding requests */
714 for (current
= private_data
->store
; current
!= NULL
;
715 current
= current
->next
) {
716 if (strcmp(current
->cookie
, paged_ctrl
->cookie
) == 0) {
717 current
->timestamp
= time(NULL
);
721 if (current
== NULL
) {
722 return LDB_ERR_UNWILLING_TO_PERFORM
;
725 /* Get the expression string and make sure it didn't change */
726 expr_str
= ldb_filter_from_tree(ac
, req
->op
.search
.tree
);
727 if (expr_str
== NULL
) {
728 return LDB_ERR_OPERATIONS_ERROR
;
731 ret
= strcmp(current
->expr_str
, expr_str
);
733 return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION
;
736 bool_ret
= paged_controls_same(req
, current
->down_controls
);
737 if (bool_ret
== false) {
738 return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION
;
741 bool_ret
= paged_attrs_same(req
->op
.search
.attrs
,
743 if (bool_ret
== false) {
744 return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION
;
747 DLIST_PROMOTE(private_data
->store
, current
);
751 /* check if it is an abandon */
753 return ldb_module_done(req
, NULL
, NULL
,
757 ret
= paged_results(ac
);
758 if (ret
!= LDB_SUCCESS
) {
759 return ldb_module_done(req
, NULL
, NULL
, ret
);
761 return ldb_module_done(req
, ac
->controls
, NULL
, LDB_SUCCESS
);
765 static int paged_request_init(struct ldb_module
*module
)
767 struct ldb_context
*ldb
;
768 struct private_data
*data
;
771 ldb
= ldb_module_get_ctx(module
);
773 data
= talloc(module
, struct private_data
);
775 return LDB_ERR_OTHER
;
778 data
->next_free_id
= 1;
779 data
->num_stores
= 0;
781 ldb_module_set_private(module
, data
);
783 ret
= ldb_mod_register_control(module
, LDB_CONTROL_PAGED_RESULTS_OID
);
784 if (ret
!= LDB_SUCCESS
) {
785 ldb_debug(ldb
, LDB_DEBUG_WARNING
,
787 "Unable to register control with rootdse!");
790 return ldb_next_init(module
);
793 static const struct ldb_module_ops ldb_paged_results_module_ops
= {
794 .name
= "dsdb_paged_results",
795 .search
= paged_search
,
796 .init_context
= paged_request_init
799 int ldb_dsdb_paged_results_init(const char *version
)
801 LDB_MODULE_CHECK_VERSION(version
);
802 return ldb_register_module(&ldb_paged_results_module_ops
);