| 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 |  |  |  |