1 |
|
|
/* $OpenBSD: search.c,v 1.18 2017/01/20 11:55:08 benno Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/queue.h> |
20 |
|
|
#include <sys/types.h> |
21 |
|
|
#include <sys/tree.h> |
22 |
|
|
|
23 |
|
|
#include <errno.h> |
24 |
|
|
#include <event.h> |
25 |
|
|
#include <stdlib.h> |
26 |
|
|
#include <string.h> |
27 |
|
|
#include <time.h> |
28 |
|
|
|
29 |
|
|
#include "ldapd.h" |
30 |
|
|
#include "log.h" |
31 |
|
|
|
32 |
|
|
#define MAX_SEARCHES 200 |
33 |
|
|
|
34 |
|
|
void filter_free(struct plan *filter); |
35 |
|
|
static int search_result(const char *dn, |
36 |
|
|
size_t dnlen, |
37 |
|
|
struct ber_element *attrs, |
38 |
|
|
struct search *search); |
39 |
|
|
|
40 |
|
|
static int |
41 |
|
|
uniqdn_cmp(struct uniqdn *a, struct uniqdn *b) |
42 |
|
|
{ |
43 |
|
|
if (a->key.size < b->key.size) |
44 |
|
|
return -1; |
45 |
|
|
if (a->key.size > b->key.size) |
46 |
|
|
return +1; |
47 |
|
|
return memcmp(a->key.data, b->key.data, a->key.size); |
48 |
|
|
} |
49 |
|
|
|
50 |
|
|
RB_GENERATE(dn_tree, uniqdn, link, uniqdn_cmp); |
51 |
|
|
|
52 |
|
|
/* Return true if the attribute is operational. |
53 |
|
|
*/ |
54 |
|
|
static int |
55 |
|
|
is_operational(char *adesc) |
56 |
|
|
{ |
57 |
|
|
struct attr_type *at; |
58 |
|
|
|
59 |
|
|
at = lookup_attribute(conf->schema, adesc); |
60 |
|
|
if (at) |
61 |
|
|
return at->usage != USAGE_USER_APP; |
62 |
|
|
|
63 |
|
|
return 0; |
64 |
|
|
} |
65 |
|
|
|
66 |
|
|
/* Return true if attr should be included in search entry. |
67 |
|
|
*/ |
68 |
|
|
static int |
69 |
|
|
should_include_attribute(char *adesc, struct search *search, int explicit) |
70 |
|
|
{ |
71 |
|
|
char *fdesc; |
72 |
|
|
struct ber_element *elm; |
73 |
|
|
|
74 |
|
|
if (search->attrlist->be_sub == NULL || |
75 |
|
|
search->attrlist->be_sub->be_encoding == BER_TYPE_EOC) { |
76 |
|
|
/* An empty list with no attributes requests the return of |
77 |
|
|
* all user attributes. */ |
78 |
|
|
return !is_operational(adesc); |
79 |
|
|
} |
80 |
|
|
|
81 |
|
|
for (elm = search->attrlist->be_sub; elm; elm = elm->be_next) { |
82 |
|
|
if (ber_get_string(elm, &fdesc) != 0) |
83 |
|
|
continue; |
84 |
|
|
if (strcasecmp(fdesc, adesc) == 0) |
85 |
|
|
return 1; |
86 |
|
|
if (strcmp(fdesc, "*") == 0 && !is_operational(adesc)) |
87 |
|
|
return 1; |
88 |
|
|
if (strcmp(fdesc, "+") == 0 && is_operational(adesc) && |
89 |
|
|
!explicit) |
90 |
|
|
return 1; |
91 |
|
|
} |
92 |
|
|
|
93 |
|
|
return 0; |
94 |
|
|
} |
95 |
|
|
|
96 |
|
|
static int |
97 |
|
|
search_result(const char *dn, size_t dnlen, struct ber_element *attrs, |
98 |
|
|
struct search *search) |
99 |
|
|
{ |
100 |
|
|
int rc; |
101 |
|
|
struct conn *conn = search->conn; |
102 |
|
|
struct ber_element *root, *elm, *filtered_attrs = NULL, *link, *a; |
103 |
|
|
struct ber_element *prev, *next; |
104 |
|
|
char *adesc; |
105 |
|
|
void *buf; |
106 |
|
|
|
107 |
|
|
if ((root = ber_add_sequence(NULL)) == NULL) |
108 |
|
|
goto fail; |
109 |
|
|
|
110 |
|
|
if ((filtered_attrs = ber_add_sequence(NULL)) == NULL) |
111 |
|
|
goto fail; |
112 |
|
|
link = filtered_attrs; |
113 |
|
|
|
114 |
|
|
for (prev = NULL, a = attrs->be_sub; a; a = next) { |
115 |
|
|
if (ber_get_string(a->be_sub, &adesc) != 0) |
116 |
|
|
goto fail; |
117 |
|
|
if (should_include_attribute(adesc, search, 0)) { |
118 |
|
|
next = a->be_next; |
119 |
|
|
if (prev != NULL) |
120 |
|
|
prev->be_next = a->be_next; /* unlink a */ |
121 |
|
|
else |
122 |
|
|
attrs->be_sub = a->be_next; |
123 |
|
|
a->be_next = NULL; /* break chain*/ |
124 |
|
|
ber_link_elements(link, a); |
125 |
|
|
link = a; |
126 |
|
|
} else { |
127 |
|
|
prev = a; |
128 |
|
|
next = a->be_next; |
129 |
|
|
} |
130 |
|
|
} |
131 |
|
|
|
132 |
|
|
elm = ber_printf_elements(root, "i{txe", search->req->msgid, |
133 |
|
|
BER_CLASS_APP, (unsigned long)LDAP_RES_SEARCH_ENTRY, |
134 |
|
|
dn, dnlen, filtered_attrs); |
135 |
|
|
if (elm == NULL) |
136 |
|
|
goto fail; |
137 |
|
|
|
138 |
|
|
ldap_debug_elements(root, LDAP_RES_SEARCH_ENTRY, |
139 |
|
|
"sending search entry on fd %d", conn->fd); |
140 |
|
|
|
141 |
|
|
rc = ber_write_elements(&conn->ber, root); |
142 |
|
|
ber_free_elements(root); |
143 |
|
|
|
144 |
|
|
if (rc < 0) { |
145 |
|
|
log_warn("failed to create search-entry response"); |
146 |
|
|
return -1; |
147 |
|
|
} |
148 |
|
|
|
149 |
|
|
ber_get_writebuf(&conn->ber, &buf); |
150 |
|
|
if (bufferevent_write(conn->bev, buf, rc) != 0) { |
151 |
|
|
log_warn("failed to send ldap result"); |
152 |
|
|
return -1; |
153 |
|
|
} |
154 |
|
|
|
155 |
|
|
return 0; |
156 |
|
|
fail: |
157 |
|
|
log_warn("search result"); |
158 |
|
|
if (root) |
159 |
|
|
ber_free_elements(root); |
160 |
|
|
return -1; |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
void |
164 |
|
|
search_close(struct search *search) |
165 |
|
|
{ |
166 |
|
|
struct uniqdn *dn, *next; |
167 |
|
|
|
168 |
|
|
for (dn = RB_MIN(dn_tree, &search->uniqdns); dn; dn = next) { |
169 |
|
|
next = RB_NEXT(dn_tree, &search->uniqdns, dn); |
170 |
|
|
RB_REMOVE(dn_tree, &search->uniqdns, dn); |
171 |
|
|
free(dn->key.data); |
172 |
|
|
free(dn); |
173 |
|
|
} |
174 |
|
|
|
175 |
|
|
btree_cursor_close(search->cursor); |
176 |
|
|
btree_txn_abort(search->data_txn); |
177 |
|
|
btree_txn_abort(search->indx_txn); |
178 |
|
|
|
179 |
|
|
if (search->req != NULL) { |
180 |
|
|
log_debug("finished search on msgid %lld", search->req->msgid); |
181 |
|
|
request_free(search->req); |
182 |
|
|
} |
183 |
|
|
TAILQ_REMOVE(&search->conn->searches, search, next); |
184 |
|
|
filter_free(search->plan); |
185 |
|
|
free(search); |
186 |
|
|
--stats.searches; |
187 |
|
|
} |
188 |
|
|
|
189 |
|
|
/* Returns true (1) if key is a direct subordinate of base. |
190 |
|
|
*/ |
191 |
|
|
int |
192 |
|
|
is_child_of(struct btval *key, const char *base) |
193 |
|
|
{ |
194 |
|
|
size_t ksz, bsz; |
195 |
|
|
char *p; |
196 |
|
|
|
197 |
|
|
if ((p = memchr(key->data, ',', key->size)) == NULL) |
198 |
|
|
return 0; |
199 |
|
|
p++; |
200 |
|
|
ksz = key->size - (p - (char *)key->data); |
201 |
|
|
bsz = strlen(base); |
202 |
|
|
return (ksz == bsz && bcmp(p, base, ksz) == 0); |
203 |
|
|
} |
204 |
|
|
|
205 |
|
|
static int |
206 |
|
|
check_search_entry(struct btval *key, struct btval *val, struct search *search) |
207 |
|
|
{ |
208 |
|
|
int rc; |
209 |
|
|
char *dn0; |
210 |
|
|
struct ber_element *elm; |
211 |
|
|
|
212 |
|
|
/* verify entry is a direct subordinate of basedn */ |
213 |
|
|
if (search->scope == LDAP_SCOPE_ONELEVEL && |
214 |
|
|
!is_child_of(key, search->basedn)) { |
215 |
|
|
log_debug("not a direct subordinate of base"); |
216 |
|
|
return 0; |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
if ((dn0 = strndup(key->data, key->size)) == NULL) { |
220 |
|
|
log_warn("malloc"); |
221 |
|
|
return 0; |
222 |
|
|
} |
223 |
|
|
|
224 |
|
|
if (!authorized(search->conn, search->ns, ACI_READ, dn0, |
225 |
|
|
LDAP_SCOPE_BASE)) { |
226 |
|
|
/* LDAP_INSUFFICIENT_ACCESS */ |
227 |
|
|
free(dn0); |
228 |
|
|
return 0; |
229 |
|
|
} |
230 |
|
|
free(dn0); |
231 |
|
|
|
232 |
|
|
if ((elm = namespace_db2ber(search->ns, val)) == NULL) { |
233 |
|
|
log_warnx("failed to parse entry [%.*s]", |
234 |
|
|
(int)key->size, (char *)key->data); |
235 |
|
|
return 0; |
236 |
|
|
} |
237 |
|
|
|
238 |
|
|
if (ldap_matches_filter(elm, search->plan) != 0) { |
239 |
|
|
ber_free_elements(elm); |
240 |
|
|
return 0; |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
rc = search_result(key->data, key->size, elm, search); |
244 |
|
|
ber_free_elements(elm); |
245 |
|
|
|
246 |
|
|
if (rc == 0) |
247 |
|
|
search->nmatched++; |
248 |
|
|
|
249 |
|
|
return rc; |
250 |
|
|
} |
251 |
|
|
|
252 |
|
|
static int |
253 |
|
|
mk_dup(struct search *search, struct btval *key) |
254 |
|
|
{ |
255 |
|
|
struct uniqdn *udn; |
256 |
|
|
|
257 |
|
|
if ((udn = calloc(1, sizeof(*udn))) == NULL) |
258 |
|
|
return BT_FAIL; |
259 |
|
|
|
260 |
|
|
if ((udn->key.data = malloc(key->size)) == NULL) { |
261 |
|
|
free(udn); |
262 |
|
|
return BT_FAIL; |
263 |
|
|
} |
264 |
|
|
bcopy(key->data, udn->key.data, key->size); |
265 |
|
|
udn->key.size = key->size; |
266 |
|
|
RB_INSERT(dn_tree, &search->uniqdns, udn); |
267 |
|
|
return BT_SUCCESS; |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
/* check if this entry was already sent */ |
271 |
|
|
static int |
272 |
|
|
is_dup(struct search *search, struct btval *key) |
273 |
|
|
{ |
274 |
|
|
struct uniqdn find; |
275 |
|
|
|
276 |
|
|
find.key.data = key->data; |
277 |
|
|
find.key.size = key->size; |
278 |
|
|
return RB_FIND(dn_tree, &search->uniqdns, &find) != NULL; |
279 |
|
|
} |
280 |
|
|
|
281 |
|
|
void |
282 |
|
|
conn_search(struct search *search) |
283 |
|
|
{ |
284 |
|
|
int i, rc = BT_SUCCESS; |
285 |
|
|
unsigned int reason = LDAP_SUCCESS; |
286 |
|
|
unsigned int op = BT_NEXT; |
287 |
|
|
time_t now; |
288 |
|
|
struct conn *conn; |
289 |
|
|
struct btree_txn *txn; |
290 |
|
|
struct btval key, ikey, val; |
291 |
|
|
|
292 |
|
|
conn = search->conn; |
293 |
|
|
|
294 |
|
|
memset(&key, 0, sizeof(key)); |
295 |
|
|
memset(&val, 0, sizeof(val)); |
296 |
|
|
|
297 |
|
|
if (search->plan->indexed) |
298 |
|
|
txn = search->indx_txn; |
299 |
|
|
else |
300 |
|
|
txn = search->data_txn; |
301 |
|
|
|
302 |
|
|
if (!search->init) { |
303 |
|
|
search->cursor = btree_txn_cursor_open(NULL, txn); |
304 |
|
|
if (search->cursor == NULL) { |
305 |
|
|
log_warn("btree_cursor_open"); |
306 |
|
|
search_close(search); |
307 |
|
|
return; |
308 |
|
|
} |
309 |
|
|
|
310 |
|
|
if (search->plan->indexed) { |
311 |
|
|
search->cindx = TAILQ_FIRST(&search->plan->indices); |
312 |
|
|
key.data = search->cindx->prefix; |
313 |
|
|
log_debug("init index scan on [%s]", key.data); |
314 |
|
|
} else { |
315 |
|
|
if (*search->basedn) |
316 |
|
|
key.data = search->basedn; |
317 |
|
|
log_debug("init full scan"); |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
if (key.data) { |
321 |
|
|
key.size = strlen(key.data); |
322 |
|
|
op = BT_CURSOR; |
323 |
|
|
} |
324 |
|
|
|
325 |
|
|
search->init = 1; |
326 |
|
|
} |
327 |
|
|
|
328 |
|
|
for (i = 0; i < 10 && rc == BT_SUCCESS; i++) { |
329 |
|
|
rc = btree_cursor_get(search->cursor, &key, &val, op); |
330 |
|
|
op = BT_NEXT; |
331 |
|
|
|
332 |
|
|
if (rc == BT_SUCCESS && search->plan->indexed) { |
333 |
|
|
log_debug("found index %.*s", key.size, key.data); |
334 |
|
|
|
335 |
|
|
if (!has_prefix(&key, search->cindx->prefix)) { |
336 |
|
|
log_debug("scanned past index prefix [%s]", |
337 |
|
|
search->cindx->prefix); |
338 |
|
|
btval_reset(&val); |
339 |
|
|
btval_reset(&key); |
340 |
|
|
rc = BT_FAIL; |
341 |
|
|
errno = ENOENT; |
342 |
|
|
} |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
if (rc == BT_FAIL && errno == ENOENT && |
346 |
|
|
search->plan->indexed > 1) { |
347 |
|
|
search->cindx = TAILQ_NEXT(search->cindx, next); |
348 |
|
|
if (search->cindx != NULL) { |
349 |
|
|
rc = BT_SUCCESS; |
350 |
|
|
memset(&key, 0, sizeof(key)); |
351 |
|
|
key.data = search->cindx->prefix; |
352 |
|
|
key.size = strlen(key.data); |
353 |
|
|
log_debug("re-init cursor on [%s]", key.data); |
354 |
|
|
op = BT_CURSOR; |
355 |
|
|
continue; |
356 |
|
|
} |
357 |
|
|
} |
358 |
|
|
|
359 |
|
|
if (rc != BT_SUCCESS) { |
360 |
|
|
if (errno != ENOENT) { |
361 |
|
|
log_warnx("btree failure"); |
362 |
|
|
reason = LDAP_OTHER; |
363 |
|
|
} |
364 |
|
|
break; |
365 |
|
|
} |
366 |
|
|
|
367 |
|
|
search->nscanned++; |
368 |
|
|
|
369 |
|
|
if (search->plan->indexed) { |
370 |
|
|
bcopy(&key, &ikey, sizeof(key)); |
371 |
|
|
memset(&key, 0, sizeof(key)); |
372 |
|
|
btval_reset(&val); |
373 |
|
|
|
374 |
|
|
rc = index_to_dn(search->ns, &ikey, &key); |
375 |
|
|
btval_reset(&ikey); |
376 |
|
|
if (rc != 0) { |
377 |
|
|
reason = LDAP_OTHER; |
378 |
|
|
break; |
379 |
|
|
} |
380 |
|
|
|
381 |
|
|
log_debug("lookup indexed key [%.*s]", |
382 |
|
|
(int)key.size, (char *)key.data); |
383 |
|
|
|
384 |
|
|
/* verify entry is a direct subordinate */ |
385 |
|
|
if (search->scope == LDAP_SCOPE_ONELEVEL && |
386 |
|
|
!is_child_of(&key, search->basedn)) { |
387 |
|
|
log_debug("not a direct subordinate of base"); |
388 |
|
|
btval_reset(&key); |
389 |
|
|
continue; |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
if (search->plan->indexed > 1 && is_dup(search, &key)) { |
393 |
|
|
log_debug("skipping duplicate dn %.*s", |
394 |
|
|
(int)key.size, (char *)key.data); |
395 |
|
|
search->ndups++; |
396 |
|
|
btval_reset(&key); |
397 |
|
|
continue; |
398 |
|
|
} |
399 |
|
|
|
400 |
|
|
rc = btree_txn_get(NULL, search->data_txn, &key, &val); |
401 |
|
|
if (rc == BT_FAIL) { |
402 |
|
|
if (errno == ENOENT) { |
403 |
|
|
log_warnx("indexed key [%.*s]" |
404 |
|
|
" doesn't exist!", |
405 |
|
|
(int)key.size, (char *)key.data); |
406 |
|
|
btval_reset(&key); |
407 |
|
|
rc = BT_SUCCESS; |
408 |
|
|
continue; |
409 |
|
|
} |
410 |
|
|
log_warnx("btree failure"); |
411 |
|
|
btval_reset(&key); |
412 |
|
|
reason = LDAP_OTHER; |
413 |
|
|
break; |
414 |
|
|
} |
415 |
|
|
} |
416 |
|
|
|
417 |
|
|
log_debug("found dn %.*s", (int)key.size, (char *)key.data); |
418 |
|
|
|
419 |
|
|
if (!has_suffix(&key, search->basedn)) { |
420 |
|
|
btval_reset(&val); |
421 |
|
|
btval_reset(&key); |
422 |
|
|
if (search->plan->indexed) |
423 |
|
|
continue; |
424 |
|
|
else { |
425 |
|
|
log_debug("scanned past basedn suffix"); |
426 |
|
|
rc = 1; |
427 |
|
|
break; |
428 |
|
|
} |
429 |
|
|
} |
430 |
|
|
|
431 |
|
|
rc = check_search_entry(&key, &val, search); |
432 |
|
|
btval_reset(&val); |
433 |
|
|
if (rc == BT_SUCCESS && search->plan->indexed > 1) |
434 |
|
|
rc = mk_dup(search, &key); |
435 |
|
|
|
436 |
|
|
btval_reset(&key); |
437 |
|
|
|
438 |
|
|
/* Check if we have passed the size limit. */ |
439 |
|
|
if (rc == BT_SUCCESS && search->szlim > 0 && |
440 |
|
|
search->nmatched >= search->szlim) { |
441 |
|
|
log_debug("search %d/%lld has reached size limit (%u)", |
442 |
|
|
search->conn->fd, search->req->msgid, |
443 |
|
|
search->szlim); |
444 |
|
|
reason = LDAP_SIZELIMIT_EXCEEDED; |
445 |
|
|
rc = BT_FAIL; |
446 |
|
|
} |
447 |
|
|
} |
448 |
|
|
|
449 |
|
|
/* Check if we have passed the time limit. */ |
450 |
|
|
now = time(0); |
451 |
|
|
if (rc == 0 && search->tmlim > 0 && |
452 |
|
|
search->started_at + search->tmlim <= now) { |
453 |
|
|
log_debug("search %d/%lld has reached time limit (%u)", |
454 |
|
|
search->conn->fd, search->req->msgid, |
455 |
|
|
search->tmlim); |
456 |
|
|
reason = LDAP_TIMELIMIT_EXCEEDED; |
457 |
|
|
rc = 1; |
458 |
|
|
++stats.timeouts; |
459 |
|
|
} |
460 |
|
|
|
461 |
|
|
if (rc == 0) { |
462 |
|
|
bufferevent_enable(search->conn->bev, EV_WRITE); |
463 |
|
|
} else { |
464 |
|
|
log_debug("%u scanned, %u matched, %u dups", |
465 |
|
|
search->nscanned, search->nmatched, search->ndups); |
466 |
|
|
send_ldap_result(conn, search->req->msgid, |
467 |
|
|
LDAP_RES_SEARCH_RESULT, reason); |
468 |
|
|
if (errno != ENOENT) |
469 |
|
|
log_debug("search failed: %s", strerror(errno)); |
470 |
|
|
search_close(search); |
471 |
|
|
} |
472 |
|
|
} |
473 |
|
|
|
474 |
|
|
static void |
475 |
|
|
ldap_search_root_dse(struct search *search) |
476 |
|
|
{ |
477 |
|
|
struct namespace *ns; |
478 |
|
|
struct ber_element *root, *elm, *key, *val; |
479 |
|
|
|
480 |
|
|
if ((root = ber_add_sequence(NULL)) == NULL) { |
481 |
|
|
return; |
482 |
|
|
} |
483 |
|
|
|
484 |
|
|
elm = ber_add_sequence(root); |
485 |
|
|
key = ber_add_string(elm, "objectClass"); |
486 |
|
|
val = ber_add_set(key); |
487 |
|
|
ber_add_string(val, "top"); |
488 |
|
|
|
489 |
|
|
elm = ber_add_sequence(elm); |
490 |
|
|
key = ber_add_string(elm, "supportedLDAPVersion"); |
491 |
|
|
val = ber_add_set(key); |
492 |
|
|
ber_add_string(val, "3"); |
493 |
|
|
|
494 |
|
|
elm = ber_add_sequence(elm); |
495 |
|
|
key = ber_add_string(elm, "namingContexts"); |
496 |
|
|
val = ber_add_set(key); |
497 |
|
|
TAILQ_FOREACH(ns, &conf->namespaces, next) |
498 |
|
|
val = ber_add_string(val, ns->suffix); |
499 |
|
|
|
500 |
|
|
elm = ber_add_sequence(elm); |
501 |
|
|
key = ber_add_string(elm, "supportedExtension"); |
502 |
|
|
val = ber_add_set(key); |
503 |
|
|
ber_add_string(val, "1.3.6.1.4.1.1466.20037"); /* StartTLS */ |
504 |
|
|
|
505 |
|
|
elm = ber_add_sequence(elm); |
506 |
|
|
key = ber_add_string(elm, "supportedFeatures"); |
507 |
|
|
val = ber_add_set(key); |
508 |
|
|
/* All Operational Attributes (RFC 3673) */ |
509 |
|
|
ber_add_string(val, "1.3.6.1.4.1.4203.1.5.1"); |
510 |
|
|
|
511 |
|
|
elm = ber_add_sequence(elm); |
512 |
|
|
key = ber_add_string(elm, "subschemaSubentry"); |
513 |
|
|
val = ber_add_set(key); |
514 |
|
|
ber_add_string(val, "cn=schema"); |
515 |
|
|
|
516 |
|
|
if ((search->conn->s_flags & F_SECURE) == F_SECURE) { |
517 |
|
|
elm = ber_add_sequence(elm); |
518 |
|
|
key = ber_add_string(elm, "supportedSASLMechanisms"); |
519 |
|
|
val = ber_add_set(key); |
520 |
|
|
ber_add_string(val, "PLAIN"); |
521 |
|
|
} |
522 |
|
|
|
523 |
|
|
search_result("", 0, root, search); |
524 |
|
|
ber_free_elements(root); |
525 |
|
|
send_ldap_result(search->conn, search->req->msgid, |
526 |
|
|
LDAP_RES_SEARCH_RESULT, LDAP_SUCCESS); |
527 |
|
|
search_close(search); |
528 |
|
|
} |
529 |
|
|
|
530 |
|
|
static void |
531 |
|
|
ldap_search_subschema(struct search *search) |
532 |
|
|
{ |
533 |
|
|
char buf[1024]; |
534 |
|
|
struct ber_element *root, *elm, *key, *val; |
535 |
|
|
struct object *obj; |
536 |
|
|
struct attr_type *at; |
537 |
|
|
int rc, i; |
538 |
|
|
|
539 |
|
|
if ((root = ber_add_sequence(NULL)) == NULL) { |
540 |
|
|
return; |
541 |
|
|
} |
542 |
|
|
|
543 |
|
|
elm = ber_add_sequence(root); |
544 |
|
|
key = ber_add_string(elm, "objectClass"); |
545 |
|
|
val = ber_add_set(key); |
546 |
|
|
val = ber_add_string(val, "top"); |
547 |
|
|
ber_add_string(val, "subschema"); |
548 |
|
|
|
549 |
|
|
elm = ber_add_sequence(elm); |
550 |
|
|
key = ber_add_string(elm, "createTimestamp"); |
551 |
|
|
val = ber_add_set(key); |
552 |
|
|
ber_add_string(val, ldap_strftime(stats.started_at)); |
553 |
|
|
|
554 |
|
|
elm = ber_add_sequence(elm); |
555 |
|
|
key = ber_add_string(elm, "modifyTimestamp"); |
556 |
|
|
val = ber_add_set(key); |
557 |
|
|
ber_add_string(val, ldap_strftime(stats.started_at)); |
558 |
|
|
|
559 |
|
|
elm = ber_add_sequence(elm); |
560 |
|
|
key = ber_add_string(elm, "subschemaSubentry"); |
561 |
|
|
val = ber_add_set(key); |
562 |
|
|
ber_add_string(val, "cn=schema"); |
563 |
|
|
|
564 |
|
|
if (should_include_attribute("objectClasses", search, 1)) { |
565 |
|
|
elm = ber_add_sequence(elm); |
566 |
|
|
key = ber_add_string(elm, "objectClasses"); |
567 |
|
|
val = ber_add_set(key); |
568 |
|
|
|
569 |
|
|
RB_FOREACH(obj, object_tree, &conf->schema->objects) { |
570 |
|
|
if (schema_dump_object(obj, buf, sizeof(buf)) != 0) { |
571 |
|
|
rc = LDAP_OTHER; |
572 |
|
|
goto done; |
573 |
|
|
} |
574 |
|
|
val = ber_add_string(val, buf); |
575 |
|
|
} |
576 |
|
|
} |
577 |
|
|
|
578 |
|
|
if (should_include_attribute("attributeTypes", search, 1)) { |
579 |
|
|
elm = ber_add_sequence(elm); |
580 |
|
|
key = ber_add_string(elm, "attributeTypes"); |
581 |
|
|
val = ber_add_set(key); |
582 |
|
|
|
583 |
|
|
RB_FOREACH(at, attr_type_tree, &conf->schema->attr_types) { |
584 |
|
|
if (schema_dump_attribute(at, buf, sizeof(buf)) != 0) { |
585 |
|
|
rc = LDAP_OTHER; |
586 |
|
|
goto done; |
587 |
|
|
} |
588 |
|
|
val = ber_add_string(val, buf); |
589 |
|
|
} |
590 |
|
|
} |
591 |
|
|
|
592 |
|
|
if (should_include_attribute("matchingRules", search, 1)) { |
593 |
|
|
elm = ber_add_sequence(elm); |
594 |
|
|
key = ber_add_string(elm, "matchingRules"); |
595 |
|
|
val = ber_add_set(key); |
596 |
|
|
|
597 |
|
|
for (i = 0; i < num_match_rules; i++) { |
598 |
|
|
if (schema_dump_match_rule(&match_rules[i], buf, |
599 |
|
|
sizeof(buf)) != 0) { |
600 |
|
|
rc = LDAP_OTHER; |
601 |
|
|
goto done; |
602 |
|
|
} |
603 |
|
|
val = ber_add_string(val, buf); |
604 |
|
|
} |
605 |
|
|
} |
606 |
|
|
|
607 |
|
|
search_result("cn=schema", 9, root, search); |
608 |
|
|
rc = LDAP_SUCCESS; |
609 |
|
|
|
610 |
|
|
done: |
611 |
|
|
ber_free_elements(root); |
612 |
|
|
send_ldap_result(search->conn, search->req->msgid, |
613 |
|
|
LDAP_RES_SEARCH_RESULT, rc); |
614 |
|
|
search_close(search); |
615 |
|
|
} |
616 |
|
|
|
617 |
|
|
static int |
618 |
|
|
add_index(struct plan *plan, const char *fmt, ...) |
619 |
|
|
{ |
620 |
|
|
struct index *indx; |
621 |
|
|
va_list ap; |
622 |
|
|
int rc; |
623 |
|
|
|
624 |
|
|
if ((indx = calloc(1, sizeof(*indx))) == NULL) |
625 |
|
|
return -1; |
626 |
|
|
|
627 |
|
|
va_start(ap, fmt); |
628 |
|
|
rc = vasprintf(&indx->prefix, fmt, ap); |
629 |
|
|
va_end(ap); |
630 |
|
|
if (rc == -1) { |
631 |
|
|
free(indx); |
632 |
|
|
return -1; |
633 |
|
|
} |
634 |
|
|
|
635 |
|
|
normalize_dn(indx->prefix); |
636 |
|
|
|
637 |
|
|
TAILQ_INSERT_TAIL(&plan->indices, indx, next); |
638 |
|
|
plan->indexed++; |
639 |
|
|
|
640 |
|
|
return 0; |
641 |
|
|
} |
642 |
|
|
|
643 |
|
|
static int |
644 |
|
|
plan_get_attr(struct plan *plan, struct namespace *ns, char *attr) |
645 |
|
|
{ |
646 |
|
|
if (ns->relax) { |
647 |
|
|
/* |
648 |
|
|
* Under relaxed schema checking, all attributes |
649 |
|
|
* are considered directory strings with case-insensitive |
650 |
|
|
* matching. |
651 |
|
|
*/ |
652 |
|
|
plan->at = lookup_attribute(conf->schema, "name"); |
653 |
|
|
plan->adesc = attr; |
654 |
|
|
} else |
655 |
|
|
plan->at = lookup_attribute(conf->schema, attr); |
656 |
|
|
|
657 |
|
|
if (plan->at == NULL) { |
658 |
|
|
log_debug("%s: no such attribute, undefined term", attr); |
659 |
|
|
return -1; |
660 |
|
|
} |
661 |
|
|
|
662 |
|
|
return 0; |
663 |
|
|
} |
664 |
|
|
|
665 |
|
|
static struct plan * |
666 |
|
|
search_planner(struct namespace *ns, struct ber_element *filter) |
667 |
|
|
{ |
668 |
|
|
int class; |
669 |
|
|
unsigned long type; |
670 |
|
|
char *s, *attr; |
671 |
|
|
struct ber_element *elm; |
672 |
|
|
struct index *indx; |
673 |
|
|
struct plan *plan, *arg = NULL; |
674 |
|
|
|
675 |
|
|
if (filter->be_class != BER_CLASS_CONTEXT) { |
676 |
|
|
log_warnx("invalid class %d in filter", filter->be_class); |
677 |
|
|
return NULL; |
678 |
|
|
} |
679 |
|
|
|
680 |
|
|
if ((plan = calloc(1, sizeof(*plan))) == NULL) { |
681 |
|
|
log_warn("search_planner: calloc"); |
682 |
|
|
return NULL; |
683 |
|
|
} |
684 |
|
|
plan->op = filter->be_type; |
685 |
|
|
TAILQ_INIT(&plan->args); |
686 |
|
|
TAILQ_INIT(&plan->indices); |
687 |
|
|
|
688 |
|
|
switch (filter->be_type) { |
689 |
|
|
case LDAP_FILT_EQ: |
690 |
|
|
case LDAP_FILT_APPR: |
691 |
|
|
if (ber_scanf_elements(filter, "{ss", &attr, &s) != 0) |
692 |
|
|
goto fail; |
693 |
|
|
if (plan_get_attr(plan, ns, attr) == -1) |
694 |
|
|
plan->undefined = 1; |
695 |
|
|
else if (plan->at->equality == NULL) { |
696 |
|
|
log_debug("'%s' doesn't define equality matching", |
697 |
|
|
attr); |
698 |
|
|
plan->undefined = 1; |
699 |
|
|
} else { |
700 |
|
|
plan->assert.value = s; |
701 |
|
|
if (namespace_has_index(ns, attr, INDEX_EQUAL)) |
702 |
|
|
add_index(plan, "%s=%s,", attr, s); |
703 |
|
|
} |
704 |
|
|
break; |
705 |
|
|
case LDAP_FILT_SUBS: |
706 |
|
|
if (ber_scanf_elements(filter, "{s{ets", |
707 |
|
|
&attr, &plan->assert.substring, &class, &type, &s) != 0) |
708 |
|
|
goto fail; |
709 |
|
|
if (plan_get_attr(plan, ns, attr) == -1) |
710 |
|
|
plan->undefined = 1; |
711 |
|
|
else if (plan->at->substr == NULL) { |
712 |
|
|
log_debug("'%s' doesn't define substring matching", |
713 |
|
|
attr); |
714 |
|
|
plan->undefined = 1; |
715 |
|
|
} else if (class == BER_CLASS_CONTEXT && |
716 |
|
|
type == LDAP_FILT_SUBS_INIT) { |
717 |
|
|
/* Only prefix substrings are usable as index. */ |
718 |
|
|
if (namespace_has_index(ns, attr, INDEX_EQUAL)) |
719 |
|
|
add_index(plan, "%s=%s", attr, s); |
720 |
|
|
} |
721 |
|
|
break; |
722 |
|
|
case LDAP_FILT_PRES: |
723 |
|
|
if (ber_scanf_elements(filter, "s", &attr) != 0) |
724 |
|
|
goto fail; |
725 |
|
|
if (plan_get_attr(plan, ns, attr) == -1) |
726 |
|
|
plan->undefined = 1; |
727 |
|
|
else if (strcasecmp(attr, "objectClass") != 0) { |
728 |
|
|
if (namespace_has_index(ns, attr, INDEX_PRESENCE)) |
729 |
|
|
add_index(plan, "%s=", attr); |
730 |
|
|
} |
731 |
|
|
break; |
732 |
|
|
case LDAP_FILT_AND: |
733 |
|
|
if (ber_scanf_elements(filter, "(e", &elm) != 0) |
734 |
|
|
goto fail; |
735 |
|
|
for (; elm; elm = elm->be_next) { |
736 |
|
|
if ((arg = search_planner(ns, elm)) == NULL) |
737 |
|
|
goto fail; |
738 |
|
|
if (arg->undefined) { |
739 |
|
|
plan->undefined = 1; |
740 |
|
|
break; |
741 |
|
|
} |
742 |
|
|
TAILQ_INSERT_TAIL(&plan->args, arg, next); |
743 |
|
|
} |
744 |
|
|
|
745 |
|
|
/* The term is undefined if any arg is undefined. */ |
746 |
|
|
if (plan->undefined) |
747 |
|
|
break; |
748 |
|
|
|
749 |
|
|
/* Select an index to use. */ |
750 |
|
|
TAILQ_FOREACH(arg, &plan->args, next) { |
751 |
|
|
if (arg->indexed) { |
752 |
|
|
while ((indx = TAILQ_FIRST(&arg->indices))) { |
753 |
|
|
TAILQ_REMOVE(&arg->indices, indx, next); |
754 |
|
|
TAILQ_INSERT_TAIL(&plan->indices, indx, |
755 |
|
|
next); |
756 |
|
|
} |
757 |
|
|
plan->indexed = arg->indexed; |
758 |
|
|
break; |
759 |
|
|
} |
760 |
|
|
} |
761 |
|
|
break; |
762 |
|
|
case LDAP_FILT_OR: |
763 |
|
|
if (ber_scanf_elements(filter, "(e", &elm) != 0) |
764 |
|
|
goto fail; |
765 |
|
|
for (; elm; elm = elm->be_next) { |
766 |
|
|
if ((arg = search_planner(ns, elm)) == NULL) |
767 |
|
|
goto fail; |
768 |
|
|
TAILQ_INSERT_TAIL(&plan->args, arg, next); |
769 |
|
|
} |
770 |
|
|
|
771 |
|
|
/* The term is undefined iff all args are undefined. */ |
772 |
|
|
plan->undefined = 1; |
773 |
|
|
TAILQ_FOREACH(arg, &plan->args, next) |
774 |
|
|
if (!arg->undefined) { |
775 |
|
|
plan->undefined = 0; |
776 |
|
|
break; |
777 |
|
|
} |
778 |
|
|
|
779 |
|
|
TAILQ_FOREACH(arg, &plan->args, next) { |
780 |
|
|
if (!arg->indexed) { |
781 |
|
|
plan->indexed = 0; |
782 |
|
|
break; |
783 |
|
|
} |
784 |
|
|
while ((indx = TAILQ_FIRST(&arg->indices))) { |
785 |
|
|
TAILQ_REMOVE(&arg->indices, indx, next); |
786 |
|
|
TAILQ_INSERT_TAIL(&plan->indices, indx,next); |
787 |
|
|
plan->indexed++; |
788 |
|
|
} |
789 |
|
|
} |
790 |
|
|
break; |
791 |
|
|
case LDAP_FILT_NOT: |
792 |
|
|
if (ber_scanf_elements(filter, "{e", &elm) != 0) |
793 |
|
|
goto fail; |
794 |
|
|
if ((arg = search_planner(ns, elm)) == NULL) |
795 |
|
|
goto fail; |
796 |
|
|
TAILQ_INSERT_TAIL(&plan->args, arg, next); |
797 |
|
|
|
798 |
|
|
plan->undefined = arg->undefined; |
799 |
|
|
if (plan->indexed) { |
800 |
|
|
log_debug("NOT filter forced unindexed search"); |
801 |
|
|
plan->indexed = 0; |
802 |
|
|
} |
803 |
|
|
break; |
804 |
|
|
|
805 |
|
|
default: |
806 |
|
|
log_warnx("filter type %d not implemented", filter->be_type); |
807 |
|
|
plan->undefined = 1; |
808 |
|
|
break; |
809 |
|
|
} |
810 |
|
|
|
811 |
|
|
return plan; |
812 |
|
|
|
813 |
|
|
fail: |
814 |
|
|
free(plan); |
815 |
|
|
return NULL; |
816 |
|
|
} |
817 |
|
|
|
818 |
|
|
void |
819 |
|
|
filter_free(struct plan *filter) |
820 |
|
|
{ |
821 |
|
|
struct index *indx; |
822 |
|
|
struct plan *arg; |
823 |
|
|
|
824 |
|
|
if (filter) { |
825 |
|
|
while ((arg = TAILQ_FIRST(&filter->args)) != NULL) { |
826 |
|
|
TAILQ_REMOVE(&filter->args, arg, next); |
827 |
|
|
filter_free(arg); |
828 |
|
|
} |
829 |
|
|
while ((indx = TAILQ_FIRST(&filter->indices)) != NULL) { |
830 |
|
|
TAILQ_REMOVE(&filter->indices, indx, next); |
831 |
|
|
free(indx->prefix); |
832 |
|
|
free(indx); |
833 |
|
|
} |
834 |
|
|
free(filter); |
835 |
|
|
} |
836 |
|
|
} |
837 |
|
|
|
838 |
|
|
int |
839 |
|
|
ldap_search(struct request *req) |
840 |
|
|
{ |
841 |
|
|
long long reason = LDAP_OTHER; |
842 |
|
|
struct referrals *refs; |
843 |
|
|
struct search *search = NULL; |
844 |
|
|
|
845 |
|
|
if (stats.searches > MAX_SEARCHES) { |
846 |
|
|
log_warnx("refusing more than %u concurrent searches", |
847 |
|
|
MAX_SEARCHES); |
848 |
|
|
reason = LDAP_BUSY; |
849 |
|
|
goto done; |
850 |
|
|
} |
851 |
|
|
++stats.searches; |
852 |
|
|
++stats.req_search; |
853 |
|
|
|
854 |
|
|
if ((search = calloc(1, sizeof(*search))) == NULL) |
855 |
|
|
return -1; |
856 |
|
|
search->req = req; |
857 |
|
|
search->conn = req->conn; |
858 |
|
|
search->init = 0; |
859 |
|
|
search->started_at = time(0); |
860 |
|
|
TAILQ_INSERT_HEAD(&req->conn->searches, search, next); |
861 |
|
|
RB_INIT(&search->uniqdns); |
862 |
|
|
|
863 |
|
|
if (ber_scanf_elements(req->op, "{sEEiibeSeS", |
864 |
|
|
&search->basedn, |
865 |
|
|
&search->scope, |
866 |
|
|
&search->deref, |
867 |
|
|
&search->szlim, |
868 |
|
|
&search->tmlim, |
869 |
|
|
&search->typesonly, |
870 |
|
|
&search->filter, |
871 |
|
|
&search->attrlist) != 0) { |
872 |
|
|
log_warnx("failed to parse search request"); |
873 |
|
|
reason = LDAP_PROTOCOL_ERROR; |
874 |
|
|
goto done; |
875 |
|
|
} |
876 |
|
|
|
877 |
|
|
normalize_dn(search->basedn); |
878 |
|
|
log_debug("base dn = %s, scope = %d", search->basedn, search->scope); |
879 |
|
|
|
880 |
|
|
if (*search->basedn == '\0') { |
881 |
|
|
/* request for the root DSE */ |
882 |
|
|
if (!authorized(req->conn, NULL, ACI_READ, "", |
883 |
|
|
LDAP_SCOPE_BASE)) { |
884 |
|
|
reason = LDAP_INSUFFICIENT_ACCESS; |
885 |
|
|
goto done; |
886 |
|
|
} |
887 |
|
|
if (search->scope != LDAP_SCOPE_BASE) { |
888 |
|
|
/* only base searches are valid */ |
889 |
|
|
reason = LDAP_NO_SUCH_OBJECT; |
890 |
|
|
goto done; |
891 |
|
|
} |
892 |
|
|
/* TODO: verify filter is (objectClass=*) */ |
893 |
|
|
ldap_search_root_dse(search); |
894 |
|
|
return 0; |
895 |
|
|
} |
896 |
|
|
|
897 |
|
|
if (strcasecmp(search->basedn, "cn=schema") == 0) { |
898 |
|
|
/* request for the subschema subentries */ |
899 |
|
|
if (!authorized(req->conn, NULL, ACI_READ, |
900 |
|
|
"cn=schema", LDAP_SCOPE_BASE)) { |
901 |
|
|
reason = LDAP_INSUFFICIENT_ACCESS; |
902 |
|
|
goto done; |
903 |
|
|
} |
904 |
|
|
if (search->scope != LDAP_SCOPE_BASE) { |
905 |
|
|
/* only base searches are valid */ |
906 |
|
|
reason = LDAP_NO_SUCH_OBJECT; |
907 |
|
|
goto done; |
908 |
|
|
} |
909 |
|
|
/* TODO: verify filter is (objectClass=subschema) */ |
910 |
|
|
ldap_search_subschema(search); |
911 |
|
|
return 0; |
912 |
|
|
} |
913 |
|
|
|
914 |
|
|
if ((search->ns = namespace_for_base(search->basedn)) == NULL) { |
915 |
|
|
refs = namespace_referrals(search->basedn); |
916 |
|
|
if (refs != NULL) { |
917 |
|
|
ldap_refer(req, search->basedn, search, refs); |
918 |
|
|
search->req = NULL; /* request free'd by ldap_refer */ |
919 |
|
|
search_close(search); |
920 |
|
|
return LDAP_REFERRAL; |
921 |
|
|
} |
922 |
|
|
log_debug("no database configured for suffix %s", |
923 |
|
|
search->basedn); |
924 |
|
|
reason = LDAP_NO_SUCH_OBJECT; |
925 |
|
|
goto done; |
926 |
|
|
} |
927 |
|
|
|
928 |
|
|
if (!authorized(req->conn, search->ns, ACI_READ, |
929 |
|
|
search->basedn, search->scope)) { |
930 |
|
|
reason = LDAP_INSUFFICIENT_ACCESS; |
931 |
|
|
goto done; |
932 |
|
|
} |
933 |
|
|
|
934 |
|
|
if (namespace_begin_txn(search->ns, &search->data_txn, |
935 |
|
|
&search->indx_txn, 1) != BT_SUCCESS) { |
936 |
|
|
if (errno == EBUSY) { |
937 |
|
|
if (namespace_queue_request(search->ns, req) != 0) { |
938 |
|
|
reason = LDAP_BUSY; |
939 |
|
|
goto done; |
940 |
|
|
} |
941 |
|
|
search->req = NULL; /* keep the scheduled request */ |
942 |
|
|
search_close(search); |
943 |
|
|
return 0; |
944 |
|
|
} |
945 |
|
|
reason = LDAP_OTHER; |
946 |
|
|
goto done; |
947 |
|
|
} |
948 |
|
|
|
949 |
|
|
if (search->scope == LDAP_SCOPE_BASE) { |
950 |
|
|
struct btval key, val; |
951 |
|
|
|
952 |
|
|
memset(&key, 0, sizeof(key)); |
953 |
|
|
memset(&val, 0, sizeof(val)); |
954 |
|
|
key.data = search->basedn; |
955 |
|
|
key.size = strlen(key.data); |
956 |
|
|
|
957 |
|
|
if (btree_txn_get(NULL, search->data_txn, &key, &val) == 0) { |
958 |
|
|
check_search_entry(&key, &val, search); |
959 |
|
|
btval_reset(&val); |
960 |
|
|
reason = LDAP_SUCCESS; |
961 |
|
|
} else if (errno == ENOENT) |
962 |
|
|
reason = LDAP_NO_SUCH_OBJECT; |
963 |
|
|
else |
964 |
|
|
reason = LDAP_OTHER; |
965 |
|
|
goto done; |
966 |
|
|
} |
967 |
|
|
|
968 |
|
|
if (!namespace_exists(search->ns, search->basedn)) { |
969 |
|
|
reason = LDAP_NO_SUCH_OBJECT; |
970 |
|
|
goto done; |
971 |
|
|
} |
972 |
|
|
|
973 |
|
|
search->plan = search_planner(search->ns, search->filter); |
974 |
|
|
if (search->plan == NULL) { |
975 |
|
|
reason = LDAP_PROTOCOL_ERROR; |
976 |
|
|
goto done; |
977 |
|
|
} |
978 |
|
|
|
979 |
|
|
if (search->plan->undefined) { |
980 |
|
|
log_debug("whole search filter is undefined"); |
981 |
|
|
reason = LDAP_SUCCESS; |
982 |
|
|
goto done; |
983 |
|
|
} |
984 |
|
|
|
985 |
|
|
if (!search->plan->indexed && search->scope == LDAP_SCOPE_ONELEVEL) { |
986 |
|
|
int sz; |
987 |
|
|
sz = strlen(search->basedn) - strlen(search->ns->suffix); |
988 |
|
|
if (sz > 0 && search->basedn[sz - 1] == ',') |
989 |
|
|
sz--; |
990 |
|
|
add_index(search->plan, "@%.*s,", sz, search->basedn); |
991 |
|
|
} |
992 |
|
|
|
993 |
|
|
if (!search->plan->indexed) |
994 |
|
|
++stats.unindexed; |
995 |
|
|
|
996 |
|
|
bufferevent_enable(req->conn->bev, EV_WRITE); |
997 |
|
|
return 0; |
998 |
|
|
|
999 |
|
|
done: |
1000 |
|
|
send_ldap_result(req->conn, req->msgid, LDAP_RES_SEARCH_RESULT, reason); |
1001 |
|
|
if (search) |
1002 |
|
|
search_close(search); |
1003 |
|
|
return 0; |
1004 |
|
|
} |
1005 |
|
|
|