summaryrefslogtreecommitdiff
path: root/usr.sbin/ldapd/index.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ldapd/index.c')
-rw-r--r--usr.sbin/ldapd/index.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/usr.sbin/ldapd/index.c b/usr.sbin/ldapd/index.c
new file mode 100644
index 00000000000..63728e84e9b
--- /dev/null
+++ b/usr.sbin/ldapd/index.c
@@ -0,0 +1,491 @@
+/* $OpenBSD: index.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2009 Martin Hedenfalk <martin@bzero.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Indices are stored as unique keys in a btree. No data is stored.
+ * The keys are made up of the attribute being indexed, concatenated
+ * with the distinguished name of the entry. The namespace suffix is
+ * stripped, however.
+ *
+ * Below, the namespace suffix dc=example,dc=com is stripped.
+ *
+ * Index b-tree sorts with plain strcmp:
+ * ...
+ * cn=Chunky Bacon,cn=chunky bacon,ou=people,
+ * cn=Chunky Bacon,uid=cbacon,ou=accounts,
+ * cn=Chunky Beans,cn=chunky beans,ou=people,
+ * cn=Chunky Beans,uid=cbeans,ou=accounts,
+ * cn=Crispy Bacon,cn=crispy bacon,ou=people,
+ * ...
+ * sn=Bacon,cn=chunky bacon,ou=people,
+ * sn=Bacon,cn=crispy bacon,ou=people,
+ * sn=Beans,cn=chunky beans,ou=people,
+ * ...
+ * This index can be used for equality, prefix and range searches.
+ *
+ * If an indexed attribute sorts numerically (integer), we might be able
+ * to just pad it with zeros... otherwise this needs to be refined.
+ *
+ * Multiple attributes can be indexed in the same database.
+ *
+ * Presence index can be stored as:
+ * !mail,cn=chunky bacon,ou=people,
+ * !mail,cn=chunky beans,ou=people,
+ * !mail,cn=crispy bacon,ou=people,
+ *
+ * Substring index could probably be stored like a suffix tree:
+ * sn>Bacon,cn=chunky bacon,ou=people,
+ * sn>acon,cn=chunky bacon,ou=people,
+ * sn>con,cn=chunky bacon,ou=people,
+ * sn>on,cn=chunky bacon,ou=people,
+ * sn>n,cn=chunky bacon,ou=people,
+ *
+ * This facilitates both substring and suffix search.
+ *
+ * Approximate index:
+ * sn~[soundex(bacon)],cn=chunky bacon,ou=people,
+ *
+ * One level searches can be indexed as follows:
+ * @<parent-rdn>,<rdn>
+ * example:
+ * @ou=accounts,uid=cbacon
+ * @ou=accounts,uid=cbeans
+ * @ou=people,cn=chunky bacon
+ * @ou=people,cn=chunky beans
+ * @ou=people,cn=crispy bacon
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldapd.h"
+
+static void continue_indexer(int fd, short why, void *arg);
+static void stop_indexer(struct ctl_conn *c);
+
+int
+index_attribute(struct namespace *ns, char *attr, struct btval *dn,
+ struct ber_element *a)
+{
+ int dnsz, rc;
+ char *s, *t;
+ struct ber_element *v;
+ struct btval key, val;
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(attr);
+ assert(dn);
+ assert(a);
+ assert(a->be_next);
+ bzero(&val, sizeof(val));
+
+ log_debug("indexing %.*s on %s", (int)dn->size, (char *)dn->data, attr);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ for (v = a->be_next->be_sub; v; v = v->be_next) {
+ if (ber_get_string(v, &s) != 0)
+ continue;
+ bzero(&key, sizeof(key));
+ key.size = asprintf(&t, "%s=%s,%.*s", attr, s, dnsz,
+ (char *)dn->data);
+ key.data = t;
+ normalize_dn(key.data);
+ rc = btree_txn_put(ns->indx_db, ns->indx_txn, &key, &val, BT_NOOVERWRITE);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+index_rdn(struct namespace *ns, struct btval *dn)
+{
+ int dnsz, rdnsz, pdnsz, rc;
+ char *t, *parent_dn;
+ struct btval key, val;
+
+ bzero(&val, sizeof(val));
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(dn);
+
+ dnsz = dn->size - strlen(ns->suffix);
+ if (dnsz-- == 0)
+ return 0;
+
+ parent_dn = memchr(dn->data, ',', dnsz);
+ if (parent_dn == NULL) {
+ rdnsz = dnsz;
+ pdnsz = 0;
+ } else {
+ rdnsz = parent_dn - (char *)dn->data;
+ pdnsz = dnsz - rdnsz - 1;
+ ++parent_dn;
+ }
+
+ key.size = asprintf(&t, "@%.*s,%.*s", pdnsz, parent_dn,
+ rdnsz, (char *)dn->data);
+ key.data = t;
+ log_debug("indexing rdn on %.*s", (int)key.size, (char *)key.data);
+ normalize_dn(key.data);
+ rc = btree_txn_put(ns->indx_db, ns->indx_txn, &key, &val, BT_NOOVERWRITE);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ return 0;
+}
+
+int
+unindex_attribute(struct namespace *ns, char *attr, struct btval *dn,
+ struct ber_element *a)
+{
+ int dnsz, rc;
+ char *s, *t;
+ struct ber_element *v;
+ struct btval key;
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(attr);
+ assert(dn);
+ assert(a);
+ assert(a->be_next);
+
+ log_debug("unindexing %.*s on %s",
+ (int)dn->size, (char *)dn->data, attr);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ for (v = a->be_next->be_sub; v; v = v->be_next) {
+ if (ber_get_string(v, &s) != 0)
+ continue;
+ bzero(&key, sizeof(key));
+ key.size = asprintf(&t, "%s=%s,%.*s", attr, s, dnsz,
+ (char *)dn->data);
+ key.data = t;
+ normalize_dn(key.data);
+ rc = btree_txn_del(ns->indx_db, ns->indx_txn, &key, NULL);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+index_entry(struct namespace *ns, struct btval *dn, struct ber_element *elm)
+{
+ struct ber_element *a;
+ struct attr_index *ai;
+
+ assert(ns);
+ assert(dn);
+ assert(elm);
+ TAILQ_FOREACH(ai, &ns->indices, next) {
+ a = ldap_get_attribute(elm, ai->attr);
+ if (a && index_attribute(ns, ai->attr, dn, a) < 0)
+ return -1;
+ }
+
+ return index_rdn(ns, dn);
+}
+
+static int
+unindex_rdn(struct namespace *ns, struct btval *dn)
+{
+ int dnsz, rdnsz, rc;
+ char *t, *parent_dn;
+ struct btval key, val;
+
+ bzero(&val, sizeof(val));
+
+ assert(ns);
+ assert(ns->indx_txn);
+ assert(dn);
+
+ dnsz = dn->size - strlen(ns->suffix);
+
+ parent_dn = memchr(dn->data, ',', dn->size);
+ if (parent_dn++ == NULL)
+ parent_dn = (char *)dn->data + dn->size;
+ rdnsz = parent_dn - (char *)dn->data;
+
+ key.size = asprintf(&t, "@%.*s,%.*s", (dnsz - rdnsz), parent_dn,
+ rdnsz, (char *)dn->data);
+ key.data = t;
+ log_debug("unindexing rdn on %.*s", (int)key.size, (char *)key.data);
+ normalize_dn(key.data);
+ rc = btree_txn_del(ns->indx_db, ns->indx_txn, &key, NULL);
+ free(t);
+ if (rc == BT_FAIL)
+ return -1;
+ return 0;
+}
+
+int
+unindex_entry(struct namespace *ns, struct btval *dn, struct ber_element *elm)
+{
+ struct ber_element *a;
+ struct attr_index *ai;
+
+ assert(ns);
+ assert(dn);
+ assert(elm);
+ TAILQ_FOREACH(ai, &ns->indices, next) {
+ a = ldap_get_attribute(elm, ai->attr);
+ if (a && unindex_attribute(ns, ai->attr, dn, a) < 0)
+ return -1;
+ }
+
+ return unindex_rdn(ns, dn);
+}
+
+/* Reconstruct the full dn from the index key and the namespace suffix.
+ *
+ * Examples:
+ *
+ * index key:
+ * sn=Bacon,cn=chunky bacon,ou=people,
+ * full dn:
+ * cn=chunky bacon,ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @ou=people,cn=chunky bacon
+ * full dn:
+ * cn=chunky bacon,ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @,ou=people
+ * full dn:
+ * ou=people,dc=example,dc=com
+ *
+ * index key:
+ * @ou=staff,ou=people,cn=chunky bacon
+ * full dn:
+ * cn=chunky bacon,ou=staff,ou=people,dc=example,dc=com
+ *
+ * Filled in dn must be freed with btval_reset().
+ */
+int
+index_to_dn(struct namespace *ns, struct btval *indx, struct btval *dn)
+{
+ char *rdn, *parent_rdn, indxtype, *dst;
+ int rdn_sz, prdn_sz;
+
+ /* Skip past first index part to get the RDN.
+ */
+
+ indxtype = ((char *)indx->data)[0];
+ if (indxtype == '@') {
+ /* One-level index.
+ * Full DN is made up of rdn + parent rdn + namespace suffix.
+ */
+ rdn = memrchr(indx->data, ',', indx->size);
+ if (rdn++ == NULL)
+ return -1;
+ rdn_sz = indx->size - (rdn - (char *)indx->data);
+
+ parent_rdn = (char *)indx->data + 1;
+ prdn_sz = rdn - parent_rdn - 1;
+ dn->size = indx->size + strlen(ns->suffix);
+ if (prdn_sz == 0)
+ dn->size--;
+ if ((dn->data = malloc(dn->size)) == NULL) {
+ log_warn("conn_search: malloc");
+ return -1;
+ }
+ dst = dn->data;
+ bcopy(rdn, dst, rdn_sz);
+ dst += rdn_sz;
+ *dst++ = ',';
+ bcopy(parent_rdn, dst, prdn_sz);
+ dst += prdn_sz;
+ if (prdn_sz > 0)
+ *dst++ = ',';
+ bcopy(ns->suffix, dst, strlen(ns->suffix));
+ } else {
+ /* Construct the full DN by appending namespace suffix.
+ */
+ rdn = memchr(indx->data, ',', indx->size);
+ if (rdn++ == NULL)
+ return -1;
+ rdn_sz = indx->size - (rdn - (char *)indx->data);
+ dn->size = rdn_sz + strlen(ns->suffix);
+ if ((dn->data = malloc(dn->size)) == NULL) {
+ log_warn("index_to_dn: malloc");
+ return -1;
+ }
+ bcopy(rdn, dn->data, rdn_sz);
+ bcopy(ns->suffix, (char *)dn->data + rdn_sz,
+ strlen(ns->suffix));
+ }
+
+ dn->free_data = 1;
+
+ return 0;
+}
+
+/* Return the next namespace that isn't already being indexed or compacted.
+ */
+static struct namespace *
+next_namespace(struct namespace *ns)
+{
+ if (ns == NULL)
+ ns = TAILQ_FIRST(&conf->namespaces);
+ else
+ ns = TAILQ_NEXT(ns, next);
+
+ do {
+ if (ns == NULL || (!ns->indexing && !ns->compacting))
+ break;
+ } while ((ns = TAILQ_NEXT(ns, next)) != NULL);
+
+ return ns;
+}
+
+static void
+continue_indexer(int fd, short why, void *arg)
+{
+ struct ctl_conn *c = arg;
+ struct ber_element *elm;
+ struct btval key, val;
+ struct timeval tv;
+ int i, rc;
+
+ if (c->cursor == NULL) {
+ log_info("begin indexing namespace %s", c->ns->suffix);
+ c->ncomplete = 0;
+ c->ns->indexing = 1;
+ c->cursor = btree_cursor_open(c->ns->data_db);
+ if (c->cursor == NULL) {
+ log_warn("failed to open cursor");
+ goto fail;
+ }
+ }
+
+ bzero(&key, sizeof(key));
+ bzero(&val, sizeof(val));
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if (namespace_begin(c->ns) != 0) {
+ tv.tv_usec = 50000;
+ evtimer_add(&c->ev, &tv);
+ return;
+ }
+
+ for (i = 0; i < 40; i++) {
+ rc = btree_cursor_get(c->cursor, &key, &val, BT_NEXT);
+ if (rc != BT_SUCCESS)
+ break;
+ if ((elm = namespace_db2ber(c->ns, &val)) == NULL)
+ continue;
+ rc = index_entry(c->ns, &key, elm);
+ ber_free_elements(elm);
+ btval_reset(&key);
+ btval_reset(&val);
+ if (rc != 0)
+ goto fail;
+ ++c->ncomplete;
+ }
+
+ if (namespace_commit(c->ns) != 0)
+ goto fail;
+
+ control_report_indexer(c, 0);
+
+ if (rc == BT_NOTFOUND) {
+ log_info("done indexing namespace %s", c->ns->suffix);
+ btree_cursor_close(c->cursor);
+ c->cursor = NULL;
+ c->ns->indexing = 0;
+ control_report_indexer(c, 1);
+
+ if (c->all)
+ c->ns = next_namespace(c->ns);
+ else
+ c->ns = NULL;
+
+ if (c->ns == NULL) {
+ log_info("done indexing all namespaces");
+ return;
+ }
+ } else if (rc != BT_SUCCESS)
+ goto fail;
+
+ evtimer_add(&c->ev, &tv);
+ return;
+
+fail:
+ if (c->ns != NULL)
+ control_report_indexer(c, -1);
+ control_end(c);
+ stop_indexer(c);
+}
+
+/* Run indexing for the given namespace, or all namespaces if ns is NULL.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int
+run_indexer(struct ctl_conn *c, struct namespace *ns)
+{
+ if (ns == NULL) {
+ c->all = 1;
+ c->ns = next_namespace(NULL);
+ } else {
+ c->all = 0;
+ c->ns = ns;
+ }
+
+ if (c->ns == NULL) {
+ control_end(c);
+ } else {
+ c->closecb = stop_indexer;
+ evtimer_set(&c->ev, continue_indexer, c);
+ continue_indexer(0, 0, c);
+ }
+
+ return 0;
+}
+
+static void
+stop_indexer(struct ctl_conn *c)
+{
+ if (c->ns != NULL) {
+ log_info("stopped indexing namespace %s", c->ns->suffix);
+ c->ns->indexing = 0;
+ }
+ btree_cursor_close(c->cursor);
+ c->cursor = NULL;
+ c->ns = NULL;
+ c->closecb = NULL;
+ evtimer_del(&c->ev);
+}
+